Rename alternative scheduler to entry_frequency

This commit is contained in:
Frédéric Guillot 2020-05-25 14:59:15 -07:00
parent cead85b165
commit 7e5157f218
8 changed files with 181 additions and 189 deletions

View file

@ -736,17 +736,17 @@ func TestDefautSchedulerCountBasedMaxIntervalValue(t *testing.T) {
t.Fatalf(`Parsing failure: %v`, err)
}
expected := defaultSchedulerCountBasedMaxInterval
result := opts.SchedulerCountBasedMaxInterval()
expected := defaultSchedulerEntryFrequencyMaxInterval
result := opts.SchedulerEntryFrequencyMaxInterval()
if result != expected {
t.Fatalf(`Unexpected SCHEDULER_ENTRY_COUNT_BASED_MAX_INTERVAL value, got %v instead of %v`, result, expected)
t.Fatalf(`Unexpected SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL value, got %v instead of %v`, result, expected)
}
}
func TestDefautSchedulerCountBasedMaxInterval(t *testing.T) {
os.Clearenv()
os.Setenv("SCHEDULER_ENTRY_COUNT_BASED_MAX_INTERVAL", "30")
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL", "30")
parser := NewParser()
opts, err := parser.ParseEnvironmentVariables()
@ -755,10 +755,10 @@ func TestDefautSchedulerCountBasedMaxInterval(t *testing.T) {
}
expected := 30
result := opts.SchedulerCountBasedMaxInterval()
result := opts.SchedulerEntryFrequencyMaxInterval()
if result != expected {
t.Fatalf(`Unexpected SCHEDULER_ENTRY_COUNT_BASED_MAX_INTERVAL value, got %v instead of %v`, result, expected)
t.Fatalf(`Unexpected SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL value, got %v instead of %v`, result, expected)
}
}
@ -771,17 +771,17 @@ func TestDefautSchedulerCountBasedMinIntervalValue(t *testing.T) {
t.Fatalf(`Parsing failure: %v`, err)
}
expected := defaultSchedulerCountBasedMinInterval
result := opts.SchedulerCountBasedMinInterval()
expected := defaultSchedulerEntryFrequencyMinInterval
result := opts.SchedulerEntryFrequencyMinInterval()
if result != expected {
t.Fatalf(`Unexpected SCHEDULER_ENTRY_COUNT_BASED_MIN_INTERVAL value, got %v instead of %v`, result, expected)
t.Fatalf(`Unexpected SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL value, got %v instead of %v`, result, expected)
}
}
func TestDefautSchedulerCountBasedMinInterval(t *testing.T) {
os.Clearenv()
os.Setenv("SCHEDULER_ENTRY_COUNT_BASED_MIN_INTERVAL", "30")
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL", "30")
parser := NewParser()
opts, err := parser.ParseEnvironmentVariables()
@ -790,10 +790,10 @@ func TestDefautSchedulerCountBasedMinInterval(t *testing.T) {
}
expected := 30
result := opts.SchedulerCountBasedMinInterval()
result := opts.SchedulerEntryFrequencyMinInterval()
if result != expected {
t.Fatalf(`Unexpected SCHEDULER_ENTRY_COUNT_BASED_MIN_INTERVAL value, got %v instead of %v`, result, expected)
t.Fatalf(`Unexpected SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL value, got %v instead of %v`, result, expected)
}
}

View file

@ -10,135 +10,135 @@ import (
)
const (
defaultHTTPS = false
defaultLogDateTime = false
defaultHSTS = true
defaultHTTPService = true
defaultSchedulerService = true
defaultDebug = false
defaultBaseURL = "http://localhost"
defaultRootURL = "http://localhost"
defaultBasePath = ""
defaultWorkerPoolSize = 5
defaultPollingFrequency = 60
defaultBatchSize = 10
defaultPollingScheduler = "round_robin"
defaultSchedulerCountBasedMinInterval = 5
defaultSchedulerCountBasedMaxInterval = 24 * 60
defaultRunMigrations = false
defaultDatabaseURL = "user=postgres password=postgres dbname=miniflux2 sslmode=disable"
defaultDatabaseMaxConns = 20
defaultDatabaseMinConns = 1
defaultListenAddr = "127.0.0.1:8080"
defaultCertFile = ""
defaultKeyFile = ""
defaultCertDomain = ""
defaultCertCache = "/tmp/cert_cache"
defaultCleanupFrequencyHours = 24
defaultCleanupArchiveReadDays = 60
defaultCleanupRemoveSessionsDays = 30
defaultProxyImages = "http-only"
defaultCreateAdmin = false
defaultOAuth2UserCreation = false
defaultOAuth2ClientID = ""
defaultOAuth2ClientSecret = ""
defaultOAuth2RedirectURL = ""
defaultOAuth2OidcDiscoveryEndpoint = ""
defaultOAuth2Provider = ""
defaultPocketConsumerKey = ""
defaultHTTPClientTimeout = 20
defaultHTTPClientMaxBodySize = 15
defaultAuthProxyHeader = ""
defaultAuthProxyUserCreation = false
defaultHTTPS = false
defaultLogDateTime = false
defaultHSTS = true
defaultHTTPService = true
defaultSchedulerService = true
defaultDebug = false
defaultBaseURL = "http://localhost"
defaultRootURL = "http://localhost"
defaultBasePath = ""
defaultWorkerPoolSize = 5
defaultPollingFrequency = 60
defaultBatchSize = 10
defaultPollingScheduler = "round_robin"
defaultSchedulerEntryFrequencyMinInterval = 5
defaultSchedulerEntryFrequencyMaxInterval = 24 * 60
defaultRunMigrations = false
defaultDatabaseURL = "user=postgres password=postgres dbname=miniflux2 sslmode=disable"
defaultDatabaseMaxConns = 20
defaultDatabaseMinConns = 1
defaultListenAddr = "127.0.0.1:8080"
defaultCertFile = ""
defaultKeyFile = ""
defaultCertDomain = ""
defaultCertCache = "/tmp/cert_cache"
defaultCleanupFrequencyHours = 24
defaultCleanupArchiveReadDays = 60
defaultCleanupRemoveSessionsDays = 30
defaultProxyImages = "http-only"
defaultCreateAdmin = false
defaultOAuth2UserCreation = false
defaultOAuth2ClientID = ""
defaultOAuth2ClientSecret = ""
defaultOAuth2RedirectURL = ""
defaultOAuth2OidcDiscoveryEndpoint = ""
defaultOAuth2Provider = ""
defaultPocketConsumerKey = ""
defaultHTTPClientTimeout = 20
defaultHTTPClientMaxBodySize = 15
defaultAuthProxyHeader = ""
defaultAuthProxyUserCreation = false
)
// Options contains configuration options.
type Options struct {
HTTPS bool
logDateTime bool
hsts bool
httpService bool
schedulerService bool
debug bool
baseURL string
rootURL string
basePath string
databaseURL string
databaseMaxConns int
databaseMinConns int
runMigrations bool
listenAddr string
certFile string
certDomain string
certCache string
certKeyFile string
cleanupFrequencyHours int
cleanupArchiveReadDays int
cleanupRemoveSessionsDays int
pollingFrequency int
batchSize int
pollingScheduler string
schedulerCountBasedMinInterval int
schedulerCountBasedMaxInterval int
workerPoolSize int
createAdmin bool
proxyImages string
oauth2UserCreationAllowed bool
oauth2ClientID string
oauth2ClientSecret string
oauth2RedirectURL string
oauth2OidcDiscoveryEndpoint string
oauth2Provider string
pocketConsumerKey string
httpClientTimeout int
httpClientMaxBodySize int64
authProxyHeader string
authProxyUserCreation bool
HTTPS bool
logDateTime bool
hsts bool
httpService bool
schedulerService bool
debug bool
baseURL string
rootURL string
basePath string
databaseURL string
databaseMaxConns int
databaseMinConns int
runMigrations bool
listenAddr string
certFile string
certDomain string
certCache string
certKeyFile string
cleanupFrequencyHours int
cleanupArchiveReadDays int
cleanupRemoveSessionsDays int
pollingFrequency int
batchSize int
pollingScheduler string
schedulerEntryFrequencyMinInterval int
schedulerEntryFrequencyMaxInterval int
workerPoolSize int
createAdmin bool
proxyImages string
oauth2UserCreationAllowed bool
oauth2ClientID string
oauth2ClientSecret string
oauth2RedirectURL string
oauth2OidcDiscoveryEndpoint string
oauth2Provider string
pocketConsumerKey string
httpClientTimeout int
httpClientMaxBodySize int64
authProxyHeader string
authProxyUserCreation bool
}
// NewOptions returns Options with default values.
func NewOptions() *Options {
return &Options{
HTTPS: defaultHTTPS,
logDateTime: defaultLogDateTime,
hsts: defaultHSTS,
httpService: defaultHTTPService,
schedulerService: defaultSchedulerService,
debug: defaultDebug,
baseURL: defaultBaseURL,
rootURL: defaultRootURL,
basePath: defaultBasePath,
databaseURL: defaultDatabaseURL,
databaseMaxConns: defaultDatabaseMaxConns,
databaseMinConns: defaultDatabaseMinConns,
runMigrations: defaultRunMigrations,
listenAddr: defaultListenAddr,
certFile: defaultCertFile,
certDomain: defaultCertDomain,
certCache: defaultCertCache,
certKeyFile: defaultKeyFile,
cleanupFrequencyHours: defaultCleanupFrequencyHours,
cleanupArchiveReadDays: defaultCleanupArchiveReadDays,
cleanupRemoveSessionsDays: defaultCleanupRemoveSessionsDays,
pollingFrequency: defaultPollingFrequency,
batchSize: defaultBatchSize,
pollingScheduler: defaultPollingScheduler,
schedulerCountBasedMinInterval: defaultSchedulerCountBasedMinInterval,
schedulerCountBasedMaxInterval: defaultSchedulerCountBasedMaxInterval,
workerPoolSize: defaultWorkerPoolSize,
createAdmin: defaultCreateAdmin,
proxyImages: defaultProxyImages,
oauth2UserCreationAllowed: defaultOAuth2UserCreation,
oauth2ClientID: defaultOAuth2ClientID,
oauth2ClientSecret: defaultOAuth2ClientSecret,
oauth2RedirectURL: defaultOAuth2RedirectURL,
oauth2OidcDiscoveryEndpoint: defaultOAuth2OidcDiscoveryEndpoint,
oauth2Provider: defaultOAuth2Provider,
pocketConsumerKey: defaultPocketConsumerKey,
httpClientTimeout: defaultHTTPClientTimeout,
httpClientMaxBodySize: defaultHTTPClientMaxBodySize * 1024 * 1024,
authProxyHeader: defaultAuthProxyHeader,
authProxyUserCreation: defaultAuthProxyUserCreation,
HTTPS: defaultHTTPS,
logDateTime: defaultLogDateTime,
hsts: defaultHSTS,
httpService: defaultHTTPService,
schedulerService: defaultSchedulerService,
debug: defaultDebug,
baseURL: defaultBaseURL,
rootURL: defaultRootURL,
basePath: defaultBasePath,
databaseURL: defaultDatabaseURL,
databaseMaxConns: defaultDatabaseMaxConns,
databaseMinConns: defaultDatabaseMinConns,
runMigrations: defaultRunMigrations,
listenAddr: defaultListenAddr,
certFile: defaultCertFile,
certDomain: defaultCertDomain,
certCache: defaultCertCache,
certKeyFile: defaultKeyFile,
cleanupFrequencyHours: defaultCleanupFrequencyHours,
cleanupArchiveReadDays: defaultCleanupArchiveReadDays,
cleanupRemoveSessionsDays: defaultCleanupRemoveSessionsDays,
pollingFrequency: defaultPollingFrequency,
batchSize: defaultBatchSize,
pollingScheduler: defaultPollingScheduler,
schedulerEntryFrequencyMinInterval: defaultSchedulerEntryFrequencyMinInterval,
schedulerEntryFrequencyMaxInterval: defaultSchedulerEntryFrequencyMaxInterval,
workerPoolSize: defaultWorkerPoolSize,
createAdmin: defaultCreateAdmin,
proxyImages: defaultProxyImages,
oauth2UserCreationAllowed: defaultOAuth2UserCreation,
oauth2ClientID: defaultOAuth2ClientID,
oauth2ClientSecret: defaultOAuth2ClientSecret,
oauth2RedirectURL: defaultOAuth2RedirectURL,
oauth2OidcDiscoveryEndpoint: defaultOAuth2OidcDiscoveryEndpoint,
oauth2Provider: defaultOAuth2Provider,
pocketConsumerKey: defaultPocketConsumerKey,
httpClientTimeout: defaultHTTPClientTimeout,
httpClientMaxBodySize: defaultHTTPClientMaxBodySize * 1024 * 1024,
authProxyHeader: defaultAuthProxyHeader,
authProxyUserCreation: defaultAuthProxyUserCreation,
}
}
@ -242,19 +242,19 @@ func (o *Options) BatchSize() int {
return o.batchSize
}
// PollingScheduler returns the scheduler used for polling feeds
// PollingScheduler returns the scheduler used for polling feeds.
func (o *Options) PollingScheduler() string {
return o.pollingScheduler
}
// SchedulerCountBasedMaxInterval returns the maximum interval in minutes for the count-based scheduler
func (o *Options) SchedulerCountBasedMaxInterval() int {
return o.schedulerCountBasedMaxInterval
// SchedulerEntryFrequencyMaxInterval returns the maximum interval in minutes for the entry frequency scheduler.
func (o *Options) SchedulerEntryFrequencyMaxInterval() int {
return o.schedulerEntryFrequencyMaxInterval
}
// SchedulerCountBasedMinInterval returns the minimum interval in minutes for the count-based scheduler
func (o *Options) SchedulerCountBasedMinInterval() int {
return o.schedulerCountBasedMinInterval
// SchedulerEntryFrequencyMinInterval returns the minimum interval in minutes for the entry frequency scheduler.
func (o *Options) SchedulerEntryFrequencyMinInterval() int {
return o.schedulerEntryFrequencyMinInterval
}
// IsOAuth2UserCreationAllowed returns true if user creation is allowed for OAuth2 users.
@ -374,8 +374,8 @@ func (o *Options) String() string {
builder.WriteString(fmt.Sprintf("POLLING_FREQUENCY: %v\n", o.pollingFrequency))
builder.WriteString(fmt.Sprintf("BATCH_SIZE: %v\n", o.batchSize))
builder.WriteString(fmt.Sprintf("POLLING_SCHEDULER: %v\n", o.pollingScheduler))
builder.WriteString(fmt.Sprintf("SCHEDULER_ENTRY_COUNT_BASED_MAX_INTERVAL: %v\n", o.schedulerCountBasedMaxInterval))
builder.WriteString(fmt.Sprintf("SCHEDULER_ENTRY_COUNT_BASED_MIN_INTERVAL: %v\n", o.schedulerCountBasedMinInterval))
builder.WriteString(fmt.Sprintf("SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL: %v\n", o.schedulerEntryFrequencyMaxInterval))
builder.WriteString(fmt.Sprintf("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL: %v\n", o.schedulerEntryFrequencyMinInterval))
builder.WriteString(fmt.Sprintf("PROXY_IMAGES: %v\n", o.proxyImages))
builder.WriteString(fmt.Sprintf("CREATE_ADMIN: %v\n", o.createAdmin))
builder.WriteString(fmt.Sprintf("POCKET_CONSUMER_KEY: %v\n", o.pocketConsumerKey))

View file

@ -139,11 +139,11 @@ func (p *Parser) parseLines(lines []string) (err error) {
case "BATCH_SIZE":
p.opts.batchSize = parseInt(value, defaultBatchSize)
case "POLLING_SCHEDULER":
p.opts.pollingScheduler = parseString(value, defaultPollingScheduler)
case "SCHEDULER_ENTRY_COUNT_BASED_MAX_INTERVAL":
p.opts.schedulerCountBasedMaxInterval = parseInt(value, defaultSchedulerCountBasedMaxInterval)
case "SCHEDULER_ENTRY_COUNT_BASED_MIN_INTERVAL":
p.opts.schedulerCountBasedMinInterval = parseInt(value, defaultSchedulerCountBasedMinInterval)
p.opts.pollingScheduler = strings.ToLower(parseString(value, defaultPollingScheduler))
case "SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL":
p.opts.schedulerEntryFrequencyMaxInterval = parseInt(value, defaultSchedulerEntryFrequencyMaxInterval)
case "SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL":
p.opts.schedulerEntryFrequencyMinInterval = parseInt(value, defaultSchedulerEntryFrequencyMinInterval)
case "PROXY_IMAGES":
p.opts.proxyImages = parseString(value, defaultProxyImages)
case "CREATE_ADMIN":

View file

@ -111,13 +111,13 @@ Refresh interval in minutes for feeds (default is 60 minutes)\&.
Number of feeds to send to the queue for each interval (default is 10)\&.
.TP
.B POLLING_SCHEDULER
The scheduler used for polling feeds. Possible values include: "round_robin", "entry_count_based"
Scheduler used for polling feeds. Possible values are "round_robin" (default) or "entry_frequency"\&.
.TP
.B SCHEDULER_ENTRY_COUNT_BASED_MAX_INTERVAL
The maximum interval in minutes for the entry-count-based scheduler
.B SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL
Maximum interval in minutes for the entry frequency scheduler (default is 24 hours)\&.
.TP
.B SCHEDULER_ENTRY_COUNT_BASED_MIN_INTERVAL
The minimum interval in minutes for the entry-count-based scheduler
.B SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL
Minimum interval in minutes for the entry frequency scheduler (default is 5 minutes)\&.
.TP
.B DATABASE_URL
Postgresql connection parameters\&.

View file

@ -7,7 +7,6 @@ package model // import "miniflux.app/model"
import (
"fmt"
"math"
"strings"
"time"
"miniflux.app/config"
@ -41,9 +40,10 @@ type Feed struct {
ReadCount int `json:"-"`
}
// List of supported schedulers.
const (
// SchedulerEntryCountBased represnets the name of the scheduler based on entry counts.
SchedulerEntryCountBased = "entry_count_based"
SchedulerRoundRobin = "round_robin"
SchedulerEntryFrequency = "entry_frequency"
)
func (f *Feed) String() string {
@ -102,24 +102,20 @@ func (f *Feed) CheckedNow() {
// ScheduleNextCheck set "next_check_at" of a feed based on the scheduler selected from the configuration.
func (f *Feed) ScheduleNextCheck(weeklyCount int) {
var nextCheckAt time.Time
switch strings.ToLower(config.Opts.PollingScheduler()) {
case SchedulerEntryCountBased:
switch config.Opts.PollingScheduler() {
case SchedulerEntryFrequency:
var intervalMinutes int
if weeklyCount == 0 {
intervalMinutes = config.Opts.SchedulerCountBasedMaxInterval()
intervalMinutes = config.Opts.SchedulerEntryFrequencyMaxInterval()
} else {
intervalMinutes = int(math.Round(float64(7*24*60) / float64(weeklyCount)))
}
intervalMinutes = int(math.Min(float64(intervalMinutes), float64(config.Opts.SchedulerCountBasedMaxInterval())))
intervalMinutes = int(math.Max(float64(intervalMinutes), float64(config.Opts.SchedulerCountBasedMinInterval())))
nextCheckAt = time.Now().Add(time.Minute * time.Duration(intervalMinutes))
intervalMinutes = int(math.Min(float64(intervalMinutes), float64(config.Opts.SchedulerEntryFrequencyMaxInterval())))
intervalMinutes = int(math.Max(float64(intervalMinutes), float64(config.Opts.SchedulerEntryFrequencyMinInterval())))
f.NextCheckAt = time.Now().Add(time.Minute * time.Duration(intervalMinutes))
default:
// round robin
// omit the interval because they are same for all feeds.
nextCheckAt = time.Now()
f.NextCheckAt = time.Now()
}
f.NextCheckAt = nextCheckAt
}
// Feeds is a list of feed

View file

@ -133,9 +133,9 @@ func TestFeedScheduleNextCheckEntryCountBasedMaxInterval(t *testing.T) {
maxInterval := 5
minInterval := 1
os.Clearenv()
os.Setenv("POLLING_SCHEDULER", "entry_count_based")
os.Setenv("SCHEDULER_ENTRY_COUNT_BASED_MAX_INTERVAL", fmt.Sprintf("%d", maxInterval))
os.Setenv("SCHEDULER_ENTRY_COUNT_BASED_MIN_INTERVAL", fmt.Sprintf("%d", minInterval))
os.Setenv("POLLING_SCHEDULER", "entry_frequency")
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL", fmt.Sprintf("%d", maxInterval))
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL", fmt.Sprintf("%d", minInterval))
var err error
parser := config.NewParser()
@ -160,9 +160,9 @@ func TestFeedScheduleNextCheckEntryCountBasedMinInterval(t *testing.T) {
maxInterval := 500
minInterval := 100
os.Clearenv()
os.Setenv("POLLING_SCHEDULER", "entry_count_based")
os.Setenv("SCHEDULER_ENTRY_COUNT_BASED_MAX_INTERVAL", fmt.Sprintf("%d", maxInterval))
os.Setenv("SCHEDULER_ENTRY_COUNT_BASED_MIN_INTERVAL", fmt.Sprintf("%d", minInterval))
os.Setenv("POLLING_SCHEDULER", "entry_frequency")
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL", fmt.Sprintf("%d", maxInterval))
os.Setenv("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL", fmt.Sprintf("%d", minInterval))
var err error
parser := config.NewParser()

View file

@ -8,6 +8,7 @@ import (
"fmt"
"time"
"miniflux.app/config"
"miniflux.app/errors"
"miniflux.app/http/client"
"miniflux.app/locale"
@ -90,13 +91,17 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error {
return errors.NewLocalizedError(errNotFound, feedID)
}
weeklyCount, parametersErr := h.store.FeedSchedulerParameters(userID, feedID)
if parametersErr != nil {
return parametersErr
weeklyEntryCount := 0
if config.Opts.PollingScheduler() == model.SchedulerEntryFrequency {
var weeklyCountErr error
weeklyEntryCount, weeklyCountErr = h.store.WeeklyFeedEntryCount(userID, feedID)
if weeklyCountErr != nil {
return weeklyCountErr
}
}
originalFeed.CheckedNow()
originalFeed.ScheduleNextCheck(weeklyCount)
originalFeed.ScheduleNextCheck(weeklyEntryCount)
request := client.New(originalFeed.FeedURL)
request.WithCredentials(originalFeed.Username, originalFeed.Password)

View file

@ -8,9 +8,7 @@ import (
"database/sql"
"errors"
"fmt"
"strings"
"miniflux.app/config"
"miniflux.app/model"
"miniflux.app/timezone"
)
@ -274,14 +272,8 @@ func (s *Storage) fetchFeeds(feedQuery, counterQuery string, args ...interface{}
return feeds, nil
}
// FeedSchedulerParameters returns the parameters used for the scheduler.
func (s *Storage) FeedSchedulerParameters(userID, feedID int64) (int, error) {
scheduler := strings.ToLower(config.Opts.PollingScheduler())
if scheduler != model.SchedulerEntryCountBased {
return 0, nil
}
var weeklyCount int
// WeeklyFeedEntryCount returns the weekly entry count for a feed.
func (s *Storage) WeeklyFeedEntryCount(userID, feedID int64) (int, error) {
query := `
SELECT
count(*)
@ -293,15 +285,14 @@ func (s *Storage) FeedSchedulerParameters(userID, feedID int64) (int, error) {
entries.published_at BETWEEN (now() - interval '1 week') AND now();
`
err := s.db.QueryRow(query, userID, feedID).Scan(
&weeklyCount,
)
var weeklyCount int
err := s.db.QueryRow(query, userID, feedID).Scan(&weeklyCount)
switch {
case err == sql.ErrNoRows:
return 0, nil
case err != nil:
return 0, fmt.Errorf(`store: unable to fetch scheduler parameters for feed #%d: %v`, feedID, err)
return 0, fmt.Errorf(`store: unable to fetch weekly count for feed #%d: %v`, feedID, err)
}
return weeklyCount, nil