fix today memory after restart
release / docker-image (push) Successful in 1m8s
release / goreleaser (push) Successful in 10m14s

This commit is contained in:
2026-02-13 09:58:37 +03:00
parent 738dfda7a0
commit 2c6e71bad5
4 changed files with 83 additions and 3 deletions
+37 -2
View File
@@ -6,6 +6,7 @@ import (
"sync"
"time"
"git.vakhrushev.me/av/remembos/internal/memos"
"git.vakhrushev.me/av/remembos/internal/search"
"git.vakhrushev.me/av/remembos/internal/storage"
)
@@ -14,6 +15,7 @@ import (
type Service struct {
selector *search.Selector
store *storage.Storage
client *memos.Client
loc *time.Location
logger *slog.Logger
@@ -22,16 +24,18 @@ type Service struct {
cached *search.Memory
}
func NewService(selector *search.Selector, store *storage.Storage, loc *time.Location, logger *slog.Logger) *Service {
func NewService(selector *search.Selector, store *storage.Storage, client *memos.Client, loc *time.Location, logger *slog.Logger) *Service {
return &Service{
selector: selector,
store: store,
client: client,
loc: loc,
logger: logger,
}
}
// GetTodayMemory returns the memory for today, caching the result.
// On cache miss (e.g. after restart), it checks the DB for a memo already shown today.
func (s *Service) GetTodayMemory(ctx context.Context) (*search.Memory, error) {
today := time.Now().In(s.loc)
dayKey := today.Format("2006-01-02")
@@ -44,6 +48,38 @@ func (s *Service) GetTodayMemory(ctx context.Context) (*search.Memory, error) {
}
s.mu.Unlock()
// Try to restore from DB — check if a memo was already shown today
dayStart := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, s.loc)
dayEnd := dayStart.AddDate(0, 0, 1)
memoName, tier, err := s.store.GetLastShownToday(ctx, dayStart.Unix(), dayEnd.Unix())
if err != nil {
s.logger.Error("failed to check today's show history", "error", err)
// Fall through to select a new one
}
if memoName != "" {
s.logger.Info("restoring today's memory from history", "memo", memoName)
memo, err := s.client.GetMemo(ctx, memoName)
if err != nil {
s.logger.Error("failed to fetch memo from history, selecting new", "memo", memoName, "error", err)
} else {
mem := &search.Memory{
Memo: memo,
Tier: tier,
Date: memo.DisplayTime,
}
s.mu.Lock()
s.cacheDay = dayKey
s.cached = mem
s.mu.Unlock()
return mem, nil
}
}
s.logger.Info("selecting new memory", "date", dayKey)
mem, err := s.selector.Select(ctx, today)
@@ -58,7 +94,6 @@ func (s *Service) GetTodayMemory(ctx context.Context) (*search.Memory, error) {
if err := s.store.RecordShow(ctx, mem.Memo.Name, mem.Tier); err != nil {
s.logger.Error("failed to record show", "error", err)
// Non-fatal: still return the memory
}
s.mu.Lock()
+28
View File
@@ -67,6 +67,34 @@ func (c *Client) ListMemos(ctx context.Context, filter string, pageSize int, pag
return &result, nil
}
// GetMemo fetches a single memo by its resource name (e.g. "memos/123").
func (c *Client) GetMemo(ctx context.Context, name string) (*Memo, error) {
reqURL := fmt.Sprintf("%s/api/v1/%s", c.baseURL, name)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, http.NoBody)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+c.token)
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("do request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("memos API returned %d: %s", resp.StatusCode, body)
}
var memo Memo
if err := json.NewDecoder(resp.Body).Decode(&memo); err != nil {
return nil, fmt.Errorf("decode memo: %w", err)
}
return &memo, nil
}
// DownloadAttachment downloads the attachment data as bytes.
func (c *Client) DownloadAttachment(ctx context.Context, att Attachment) ([]byte, error) {
var reqURL string
+17
View File
@@ -2,6 +2,8 @@ package storage
import (
"context"
"database/sql"
"errors"
"fmt"
"strings"
"time"
@@ -65,6 +67,21 @@ func (s *Storage) GetShowCounts(ctx context.Context, memoNames []string) (map[st
return result, rows.Err()
}
// GetLastShownToday returns the memo_name and tier of the most recently shown memo
// within the given time range [from, to). Returns empty string if nothing was shown.
func (s *Storage) GetLastShownToday(ctx context.Context, from, to int64) (memoName string, tier int, err error) {
err = s.db.QueryRowContext(ctx,
`SELECT memo_name, tier FROM show_history WHERE shown_at >= ? AND shown_at < ? ORDER BY shown_at DESC LIMIT 1`,
from, to).Scan(&memoName, &tier)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return "", 0, nil
}
return "", 0, fmt.Errorf("get last shown today: %w", err)
}
return memoName, tier, nil
}
// RecordShow records that a memo was shown.
func (s *Storage) RecordShow(ctx context.Context, memoName string, tier int) error {
_, err := s.db.ExecContext(ctx,