| -rw-r--r-- | internal/router/api-v1-like.go | 117 | ||||
| -rw-r--r-- | internal/router/client-cache.go | 82 | ||||
| -rw-r--r-- | internal/router/lang-blog-title.go | 1 | ||||
| -rw-r--r-- | main.go | 2 | ||||
| -rw-r--r-- | static/input.css | 36 | ||||
| -rw-r--r-- | views/layouts/general-page.html | 14 | ||||
| -rw-r--r-- | views/pages/blog-catalogue.html | 2 | ||||
| -rw-r--r-- | views/pages/blog-page.html | 6 | ||||
| -rw-r--r-- | views/pages/global-map.html | 2 | ||||
| -rw-r--r-- | views/partials/blog-page-like-button.html | 6 |
10 files changed, 27 insertions, 241 deletions
diff --git a/internal/router/api-v1-like.go b/internal/router/api-v1-like.go deleted file mode 100644 index 1732c77..0000000 --- a/internal/router/api-v1-like.go +++ /dev/null @@ -1,117 +0,0 @@ -package router - -import ( - "fmt" - "log/slog" - "net/url" - "strconv" - "strings" - - "github.com/SayaAndy/saya-today-web/internal/b2" - "github.com/gofiber/fiber/v2" -) - -func init() { - tm.Add("blog-page-like-button", "views/partials/blog-page-like-button.html") -} - -func Api_V1_Like_Put(b2 *b2.B2Client) func(c *fiber.Ctx) error { - return func(c *fiber.Ctx) error { - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - - referer := c.Get("Referer", "") - if referer == "" { - return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is empty") - } - urlStruct, err := url.ParseRequestURI(referer) - if err != nil { - return c.Status(fiber.ErrBadRequest.Code).SendString(fmt.Sprintf("'Referer' header is invalid: %s", err.Error())) - } - - path := urlStruct.EscapedPath() - pathParts := strings.Split(strings.Trim(path, "/"), "/") - if len(pathParts) != 3 { - return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/blog/{page}'") - } - - lang, page := pathParts[0], pathParts[2] - - pageLink := lang + "/" + page + ".md" - if pages, _ := b2.Scan(pageLink); len(pages) == 0 { - return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server did not find '%s' article", pageLink)) - } - - ip := c.IP() - newLikeStatus, err := strconv.ParseBool(c.FormValue("like", "true")) - if err != nil { - return c.Status(fiber.ErrBadRequest.Code).SendString("invalid 'like' value") - } - - if newLikeStatus { - CCache.LikeOn(ip, page) - } else { - CCache.LikeOff(ip, page) - } - - slog.Debug("someone pressed the like button!", slog.String("ip", ip), slog.String("page", page), slog.String("new_like_status", fmt.Sprint(newLikeStatus))) - if c.Get("HX-Request", "false") == "true" { - content, err := tm.Render("blog-page-like-button", fiber.Map{ - "Liked": newLikeStatus, - }) - if err != nil { - slog.Warn("failed to generate div", slog.String("path", path), slog.String("error", err.Error())) - return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") - } - c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) - return c.Status(fiber.StatusOK).Send(content) - } - - return c.Status(fiber.StatusOK).SendString(fmt.Sprint(newLikeStatus)) - } -} - -func Api_V1_Like_Get(b2 *b2.B2Client) func(c *fiber.Ctx) error { - return func(c *fiber.Ctx) error { - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - - referer := c.Get("Referer", "") - if referer == "" { - return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is empty") - } - urlStruct, err := url.ParseRequestURI(referer) - if err != nil { - return c.Status(fiber.ErrBadRequest.Code).SendString(fmt.Sprintf("'Referer' header is invalid: %s", err.Error())) - } - - path := urlStruct.EscapedPath() - pathParts := strings.Split(strings.Trim(path, "/"), "/") - if len(pathParts) != 3 { - return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/blog/{page}'") - } - - lang, page := pathParts[0], pathParts[2] - - pageLink := lang + "/" + page + ".md" - if pages, _ := b2.Scan(pageLink); len(pages) == 0 { - return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server did not find '%s' article", pageLink)) - } - - ip := c.IP() - likeStatus := CCache.GetLikeStatus(ip, page) - - slog.Debug("someone requested the like status!", slog.String("ip", ip), slog.String("page", page), slog.Bool("like_status", likeStatus)) - if c.Get("HX-Request", "false") == "true" { - content, err := tm.Render("blog-page-like-button", fiber.Map{ - "Liked": likeStatus, - }) - if err != nil { - slog.Warn("failed to generate div", slog.String("path", path), slog.String("error", err.Error())) - return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") - } - c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8) - return c.Status(fiber.StatusOK).Send(content) - } - - return c.Status(fiber.StatusOK).SendString(fmt.Sprint(likeStatus)) - } -} diff --git a/internal/router/client-cache.go b/internal/router/client-cache.go index 3104a66..a4a0cc2 100644 --- a/internal/router/client-cache.go +++ b/internal/router/client-cache.go @@ -9,90 +9,40 @@ import ( ) type ClientCache struct { - hashMap map[string]string - mutexLikeMap map[string]*sync.Mutex - mutexHashMap map[string]*sync.Mutex - likePageMap map[string]map[string]struct{} - salt []byte + hashMap map[string][]byte + mutexMap map[string]*sync.Mutex + salt []byte } var CCache *ClientCache func NewClientCache(salt []byte) *ClientCache { return &ClientCache{ - hashMap: make(map[string]string), - mutexLikeMap: make(map[string]*sync.Mutex), - mutexHashMap: make(map[string]*sync.Mutex), - likePageMap: make(map[string]map[string]struct{}), - salt: salt, + hashMap: make(map[string][]byte), + mutexMap: make(map[string]*sync.Mutex), + salt: salt, } } -func (c *ClientCache) GetHash(id string) string { +func (c *ClientCache) GetHash(id string) []byte { + if val, ok := c.hashMap[id]; ok { - slog.Debug("gave an old hash", slog.String("hash", val)) + slog.Debug("gave an old hash", slog.String("hash", base64.RawStdEncoding.EncodeToString(val))) return val } - if _, ok := c.mutexHashMap[id]; !ok { - c.mutexHashMap[id] = &sync.Mutex{} + if _, ok := c.mutexMap[id]; !ok { + c.mutexMap[id] = &sync.Mutex{} } - c.mutexHashMap[id].Lock() - defer c.mutexHashMap[id].Unlock() + c.mutexMap[id].Lock() + defer c.mutexMap[id].Unlock() if val, ok := c.hashMap[id]; ok { - slog.Debug("gave a newly generated hash", slog.String("hash", val)) + slog.Debug("gave a newly generated hash", slog.String("hash", base64.RawStdEncoding.EncodeToString(val))) return val } - c.hashMap[id] = base64.RawStdEncoding.EncodeToString(argon2.IDKey([]byte(id), c.salt, 1, 64*1024, 4, 32)) - slog.Debug("generated hash", slog.String("hash", c.hashMap[id])) + c.hashMap[id] = argon2.IDKey([]byte(id), c.salt, 1, 64*1024, 4, 32) + slog.Debug("generated hash", slog.String("hash", base64.RawStdEncoding.EncodeToString(c.hashMap[id]))) return c.hashMap[id] } - -func (c *ClientCache) GetLikeStatus(id string, page string) bool { - if _, ok := c.likePageMap[page]; !ok { - return false - } - _, ok := c.likePageMap[page][c.GetHash(id)] - return ok -} - -func (c *ClientCache) LikeOn(id string, page string) (alreadyLiked bool) { - if _, ok := c.mutexLikeMap[id]; !ok { - c.mutexLikeMap[id] = &sync.Mutex{} - } - c.mutexLikeMap[id].Lock() - defer c.mutexLikeMap[id].Unlock() - - hash := c.GetHash(id) - - if userSet, ok := c.likePageMap[page]; ok { - _, alreadyLiked = userSet[hash] - c.likePageMap[page][hash] = struct{}{} - return - } - - c.likePageMap[page] = make(map[string]struct{}) - c.likePageMap[page][hash] = struct{}{} - return -} - -func (c *ClientCache) LikeOff(id string, page string) (alreadyUnliked bool) { - if _, ok := c.mutexLikeMap[id]; !ok { - c.mutexLikeMap[id] = &sync.Mutex{} - } - c.mutexLikeMap[id].Lock() - defer c.mutexLikeMap[id].Unlock() - - if _, ok := c.likePageMap[page]; !ok { - return true - } - hash := c.GetHash(id) - if _, ok := c.likePageMap[page][hash]; !ok { - return true - } - - delete(c.likePageMap[page], hash) - return false -} diff --git a/internal/router/lang-blog-title.go b/internal/router/lang-blog-title.go index bafb0d2..5b80ec8 100644 --- a/internal/router/lang-blog-title.go +++ b/internal/router/lang-blog-title.go @@ -24,6 +24,7 @@ func Lang_Blog_Title(l map[string]*locale.LocaleConfig, langs []string, b2Client return func(c *fiber.Ctx) error { ip := c.IP() slog.Debug("client entering blog page", slog.String("ip", ip), slog.String("page", c.Path())) + go CCache.GetHash(ip) lang := c.Params("lang") if !slices.Contains(langs, lang) { @@ -88,8 +88,6 @@ func main() { app.Get("/:lang/blog/:title", router.Lang_Blog_Title(localization, availableLanguages, b2Client, md)) app.Get("/api/v1/tz", router.Api_V1_TZ()) app.Get("/api/v1/blog-search", router.Api_V1_BlogSearch(localization, availableLanguages, b2Client)) - app.Get("/api/v1/like", router.Api_V1_Like_Get(b2Client)) - app.Put("/api/v1/like", router.Api_V1_Like_Put(b2Client)) app.Static("/", "./static") diff --git a/static/input.css b/static/input.css index 18910eb..9f834a6 100644 --- a/static/input.css +++ b/static/input.css @@ -139,18 +139,10 @@ a:hover { color: var(--main-medium-color); } -.text-main-light { - color: var(--main-light-color); -} - .text-background-dark { color: var(--background-dark-color); } -.text-background-light { - color: var(--background-dark-color); -} - .text-secondary { color: var(--secondary-color); } @@ -159,14 +151,6 @@ a:hover { background-color: var(--main-dark-color); } -.bg-main-medium { - background-color: var(--main-medium-color); -} - -.bg-main-light { - background-color: var(--main-light-color); -} - .bg-background-dark { background-color: var(--background-dark-color); } @@ -183,26 +167,6 @@ a:hover { border-color: var(--main-medium-color); } -.border-main-light { - border-color: var(--main-light-color); -} - -.border-background-dark { - border-color: var(--background-dark-color); -} - -.border-background-light { - border-color: var(--background-light-color); -} - -.border-inset { - border-style: inset; -} - -.border-outset { - border-style: outset; -} - .desktop-sidebar-custom { width: 5vw; background-size: calc(var(--squares-and-triangles-background-size) * 1vw); diff --git a/views/layouts/general-page.html b/views/layouts/general-page.html index 9885d66..cd19bd0 100644 --- a/views/layouts/general-page.html +++ b/views/layouts/general-page.html @@ -8,7 +8,7 @@ <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Spectral:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,200;1,300;1,400;1,500;1,600;1,700;1,800&family=Patua+One&display=swap" rel="stylesheet"> - {{ block "top-embeds" . }}{{ end }} + {{ block "header" . }}{{ end }} <link href="/output.css" rel="stylesheet"> </head> @@ -66,19 +66,17 @@ <p class="text-[2vmax] font-spectral italic text-left mt-auto">{{ .Title }}</p> <p id="published-date" class="grow text-[1.5vmax] font-spectral text-secondary text-right mt-auto">{{ .PublishedDate }}</p> </div> - {{ block "header" . }}{{ end }} <hr class="border-t-4 border-dotted border-main-dark mb-[0.8vmin]"> {{ block "body" . }}{{ end }} <div class="flex flex-row mt-[0.8vmin] mb-[0.4vmin]"> - {{ block "footer" . }}{{ end }} - <p class="grow content-center text-[1vmax]/[1] font-spectral italic text-right text-secondary mr-2"> + <p class="grow text-[0.8vmax]/[0.9] font-spectral italic text-right text-secondary mr-2"> All the photos on <a href="https://saya.today/">saya.today</a> are licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a> by <a href="https://t.me/EarlInisMona">Saya Andy</a> © {{ .PublishedYear }} </p> - <img class="max-w-[1vmax] max-h-[1vmax] self-center" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" alt="CC"> - <img class="max-w-[1vmax] max-h-[1vmax] self-center" src="https://mirrors.creativecommons.org/presskit/icons/by.svg" alt="BY"> - <img class="max-w-[1vmax] max-h-[1vmax] self-center" src="https://mirrors.creativecommons.org/presskit/icons/sa.svg" alt="SA"> + <img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" alt="" style="max-width:1.2vh; max-height:1.2vh;"> + <img src="https://mirrors.creativecommons.org/presskit/icons/by.svg" alt="" style="max-width:1.2vh; max-height:1.2vh;"> + <img src="https://mirrors.creativecommons.org/presskit/icons/sa.svg" alt="" style="max-width:1.2vh; max-height:1.2vh;"> </div> <hr class="border-t-4 border-dotted border-main-dark mb-[0.8vmin]"> @@ -211,7 +209,7 @@ }); </script> - {{ block "bottom-embeds" . }}{{ end }} + {{ block "footer" . }}{{ end }} </body> </html> diff --git a/views/pages/blog-catalogue.html b/views/pages/blog-catalogue.html index 25b0685..9fa8ea7 100644 --- a/views/pages/blog-catalogue.html +++ b/views/pages/blog-catalogue.html @@ -56,7 +56,7 @@ </div> {{ end }} -{{ define "bottom-embeds" }} +{{ define "footer" }} <script> function selectAll() { const tagsContainers = document.querySelectorAll('form.tags-list'); diff --git a/views/pages/blog-page.html b/views/pages/blog-page.html index 83860d7..755744d 100644 --- a/views/pages/blog-page.html +++ b/views/pages/blog-page.html @@ -1,4 +1,4 @@ -{{ define "top-embeds" }} +{{ define "header" }} <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lightgallery/2.8.3/css/lightgallery.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lightgallery/2.8.3/css/lg-zoom.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lightgallery/2.8.3/css/lg-thumbnail.min.css"> @@ -26,10 +26,6 @@ {{ end }} {{ define "footer" }} -<div hx-get="/api/v1/like" hx-target="this" hx-swap="outerHTML" hx-trigger="load"></div> -{{ end }} - -{{ define "bottom-embeds" }} <script src="https://f003.backblazeb2.com/file/sayana-static/libs/lightgallery/2.8.3/lightgallery.min.js"></script> <script src="https://f003.backblazeb2.com/file/sayana-static/libs/lightgallery/2.8.3/plugins/lg-zoom.min.js"></script> <script src="https://f003.backblazeb2.com/file/sayana-static/libs/lightgallery/2.8.3/plugins/lg-thumbnail.min.js"></script> diff --git a/views/pages/global-map.html b/views/pages/global-map.html index 569166f..6185076 100644 --- a/views/pages/global-map.html +++ b/views/pages/global-map.html @@ -263,5 +263,7 @@ }); </script> + {{ block "footer" . }}{{ end }} + </body> </html> diff --git a/views/partials/blog-page-like-button.html b/views/partials/blog-page-like-button.html deleted file mode 100644 index 50a9d7a..0000000 --- a/views/partials/blog-page-like-button.html +++ /dev/null @@ -1,6 +0,0 @@ -<button - hx-put="/api/v1/like" hx-vals='{"like": {{ not .Liked }}}' hx-target="this" hx-swap="outerHTML" hx-trigger="click" - class="text-[0.8vmax]/[0.9] font-spectral text-left px-[0.4vmax] py-[0.2vmax] {{ if .Liked }}bg-main-light hover:bg-main-medium{{ else }}bg-main-dark hover:bg-main-medium{{ end }} text-background-dark cursor-pointer {{ if .Liked }}border-inset{{ else }}border-outset{{ end }} border-[0.3vmax] border-background-dark"> - <i class="fas fa-thumbs-up w-[0.8vmax] h-[0.8vmax] mr-[0.4vmax]"></i> - <span>Нраица!</span> -</button>
\ No newline at end of file |