add web service
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetCooldownMemoNames returns memo names shown within the last cooldownDays.
|
||||
func (s *Storage) GetCooldownMemoNames(ctx context.Context, cooldownDays int) (map[string]struct{}, error) {
|
||||
cutoff := time.Now().Unix() - int64(cooldownDays)*86400
|
||||
|
||||
rows, err := s.db.QueryContext(ctx,
|
||||
`SELECT DISTINCT memo_name FROM show_history WHERE shown_at > ?`, cutoff)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query cooldown memos: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
result := make(map[string]struct{})
|
||||
for rows.Next() {
|
||||
var name string
|
||||
if err := rows.Scan(&name); err != nil {
|
||||
return nil, fmt.Errorf("scan memo_name: %w", err)
|
||||
}
|
||||
result[name] = struct{}{}
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
// GetShowCounts returns show_count for each of the given memo names.
|
||||
// Memos not present in history will be absent from the result (count = 0).
|
||||
func (s *Storage) GetShowCounts(ctx context.Context, memoNames []string) (map[string]int, error) {
|
||||
if len(memoNames) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
placeholders := make([]string, len(memoNames))
|
||||
args := make([]any, len(memoNames))
|
||||
for i, name := range memoNames {
|
||||
placeholders[i] = "?"
|
||||
args[i] = name
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(
|
||||
`SELECT memo_name, COUNT(*) FROM show_history WHERE memo_name IN (%s) GROUP BY memo_name`,
|
||||
strings.Join(placeholders, ","))
|
||||
|
||||
rows, err := s.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query show counts: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
result := make(map[string]int)
|
||||
for rows.Next() {
|
||||
var name string
|
||||
var count int
|
||||
if err := rows.Scan(&name, &count); err != nil {
|
||||
return nil, fmt.Errorf("scan show count: %w", err)
|
||||
}
|
||||
result[name] = count
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
// RecordShow records that a memo was shown.
|
||||
func (s *Storage) RecordShow(ctx context.Context, memoName string, tier int) error {
|
||||
_, err := s.db.ExecContext(ctx,
|
||||
`INSERT INTO show_history (memo_name, shown_at, tier) VALUES (?, ?, ?)`,
|
||||
memoName, time.Now().Unix(), tier)
|
||||
if err != nil {
|
||||
return fmt.Errorf("insert show history: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func Open(path string) (*Storage, error) {
|
||||
db, err := sql.Open("sqlite", path+"?_pragma=journal_mode(wal)&_pragma=busy_timeout(5000)")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open sqlite: %w", err)
|
||||
}
|
||||
|
||||
if err := db.Ping(); err != nil {
|
||||
db.Close()
|
||||
return nil, fmt.Errorf("ping sqlite: %w", err)
|
||||
}
|
||||
|
||||
s := &Storage{db: db}
|
||||
if err := s.migrate(context.Background()); err != nil {
|
||||
db.Close()
|
||||
return nil, fmt.Errorf("migrate: %w", err)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Storage) Close() error {
|
||||
return s.db.Close()
|
||||
}
|
||||
|
||||
func (s *Storage) migrate(ctx context.Context) error {
|
||||
const ddl = `
|
||||
CREATE TABLE IF NOT EXISTS show_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
memo_name TEXT NOT NULL,
|
||||
shown_at INTEGER NOT NULL,
|
||||
tier INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_show_history_memo ON show_history(memo_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_show_history_shown_at ON show_history(shown_at);
|
||||
`
|
||||
_, err := s.db.ExecContext(ctx, ddl)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user