Documentation
¶
Index ¶
- Constants
- Variables
- type AdminToken
- type ArticleData
- type Atom
- type AtomAuthor
- type AtomContent
- type AtomEntry
- type AtomLink
- type AuditService
- func (s *AuditService) GetAuditLogs(limit, offset int, filters map[string]interface{}) ([]database.AuditLog, error)
- func (s *AuditService) LogAdminAction(adminUserID int, adminEmail string, operationType string, targetUserID int, ...) error
- func (s *AuditService) LogFailure(adminUserID int, adminEmail string, operationType string, targetUserID int, ...) error
- func (s *AuditService) LogSuccess(adminUserID int, adminEmail string, operationType string, targetUserID int, ...) error
- type Channel
- type CheckoutSessionRequest
- type CheckoutSessionResponse
- type DomainRateLimiter
- type DomainStats
- type ErrorDetails
- type FeedData
- type FeedDiscovery
- type FeedScheduler
- type FeedService
- func (fs *FeedService) AddFeed(url string) (*database.Feed, error)
- func (fs *FeedService) AddFeedForUser(userID int, inputURL string) (*database.Feed, error)
- func (fs *FeedService) DeleteFeed(id int) error
- func (fs *FeedService) ExportOPML(userID int) ([]byte, error)
- func (fs *FeedService) FindArticleByURL(url string) (*database.Article, error)
- func (fs *FeedService) GetArticles(feedID int) ([]database.Article, error)
- func (fs *FeedService) GetFeeds() ([]database.Feed, error)
- func (fs *FeedService) GetUserArticles(userID int) ([]database.Article, error)
- func (fs *FeedService) GetUserArticlesPaginated(userID int, limit int, cursor string, unreadOnly bool) (*database.ArticlePaginationResult, error)
- func (fs *FeedService) GetUserFeedArticles(userID, feedID int) ([]database.Article, error)
- func (fs *FeedService) GetUserFeeds(userID int) ([]database.Feed, error)
- func (fs *FeedService) GetUserUnreadCounts(userID int, userFeeds []database.Feed) (map[int]int, error)
- func (fs *FeedService) ImportOPML(userID int, opmlData []byte) (int, error)
- func (fs *FeedService) ImportOPMLWithLimits(userID int, opmlData []byte, subscriptionService *SubscriptionService) (int, error)
- func (fs *FeedService) MarkAllArticlesRead(userID int) (int, error)
- func (fs *FeedService) MarkUserArticleRead(userID, articleID int, isRead bool) error
- func (fs *FeedService) RefreshFeeds() error
- func (fs *FeedService) SetHTTPClient(client HTTPClient)
- func (fs *FeedService) ToggleUserArticleStar(userID, articleID int) error
- func (fs *FeedService) UnsubscribeUserFromFeed(userID, feedID int) error
- func (fs *FeedService) UpdateUserMaxArticlesOnFeedAdd(userID, maxArticles int) error
- type HTTPClient
- type Item
- type OPML
- type OPMLBody
- type OPMLHead
- type OPMLOutline
- type PaymentService
- func (ps *PaymentService) CreateCheckoutSession(req CheckoutSessionRequest) (*CheckoutSessionResponse, error)
- func (ps *PaymentService) CreateCustomerPortalSession(userID int, returnURL string) (string, error)
- func (ps *PaymentService) CreateProductAndPrice() (*stripe.Price, error)
- func (ps *PaymentService) GetStripePublishableKey() string
- func (ps *PaymentService) GetStripeWebhookSecret() string
- func (ps *PaymentService) HandleSubscriptionUpdate(subscriptionID string) error
- func (ps *PaymentService) ValidateStripeConfig() error
- type RDF
- type RDFChannel
- type RDFItem
- type RSS
- type RateLimitError
- type RateLimiterConfig
- type ScheduledFeed
- type SchedulerConfig
- type SchedulerStatus
- type SubscriptionInfo
- type SubscriptionService
- func (ss *SubscriptionService) CanUserAddFeed(userID int) error
- func (ss *SubscriptionService) GenerateAdminToken(description string) (string, error)
- func (ss *SubscriptionService) GetDB() database.Database
- func (ss *SubscriptionService) GetUserByEmail(email string) (*database.User, error)
- func (ss *SubscriptionService) GetUserSubscriptionInfo(userID int) (*SubscriptionInfo, error)
- func (ss *SubscriptionService) GrantFreeMonths(userID int, months int) error
- func (ss *SubscriptionService) HasAdminTokens() (bool, error)
- func (ss *SubscriptionService) ListAdminTokens() ([]AdminToken, error)
- func (ss *SubscriptionService) RevokeAdminToken(tokenID int) error
- func (ss *SubscriptionService) SetUserAdmin(userID int, isAdmin bool) error
- func (ss *SubscriptionService) UpdateUserSubscription(userID int, status, subscriptionID string, ...) error
- func (ss *SubscriptionService) ValidateAdminToken(token string) (bool, error)
- type URLValidator
Constants ¶
const ( ErrorCodeInvalidURL = "invalid_url" ErrorCodeFeedNotFound = "feed_not_found" ErrorCodeFeedTimeout = "feed_timeout" ErrorCodeInvalidFormat = "invalid_feed_format" ErrorCodeNetworkError = "network_error" ErrorCodeSSRFBlocked = "ssrf_blocked" ErrorCodeDatabaseError = "database_error" ErrorCodeUnknown = "unknown_error" ErrorCodeLimitReached = "limit_reached" ErrorCodeTrialExpired = "trial_expired" ErrorCodeAlreadySubscribed = "already_subscribed" )
Error codes for frontend consumption
const (
FreeTrialFeedLimit = 20
)
Variables ¶
var ( // ErrInvalidURL indicates the provided URL is malformed or invalid ErrInvalidURL = errors.New("invalid URL") // ErrFeedNotFound indicates no RSS/Atom feeds were found at the URL ErrFeedNotFound = errors.New("feed not found") // ErrFeedTimeout indicates the feed discovery/fetch exceeded the timeout ErrFeedTimeout = errors.New("feed timeout") // ErrInvalidFeedFormat indicates the feed has invalid XML or unsupported format ErrInvalidFeedFormat = errors.New("invalid feed format") // ErrNetworkError indicates network-level failures (DNS, connection, etc.) ErrNetworkError = errors.New("network error") // ErrSSRFBlocked indicates the URL was blocked by SSRF protection ErrSSRFBlocked = errors.New("SSRF protection") // ErrDatabaseError indicates a database operation failed ErrDatabaseError = errors.New("database error") )
Feed-related error types for better error handling and user experience
var ( ErrFeedLimitReached = errors.New("feed limit reached for trial users") ErrTrialExpired = errors.New("trial period has expired") )
Functions ¶
This section is empty.
Types ¶
type AdminToken ¶
type AdminToken struct {
ID int `db:"id" json:"id"`
TokenHash string `db:"token_hash" json:"-"` // Never expose in JSON
CreatedAt time.Time `db:"created_at" json:"created_at"`
LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"`
Description string `db:"description" json:"description"`
IsActive bool `db:"is_active" json:"is_active"`
}
AdminToken represents a secure admin authentication token
type ArticleData ¶
type AtomAuthor ¶
type AtomAuthor struct {
Name string `xml:"name"`
}
type AtomContent ¶
type AtomEntry ¶
type AtomEntry struct {
Title string `xml:"title"`
Link AtomLink `xml:"link"`
Summary string `xml:"summary"`
Content AtomContent `xml:"content"`
Author AtomAuthor `xml:"author"`
Published string `xml:"published"`
Updated string `xml:"updated"`
}
type AuditService ¶
type AuditService struct {
// contains filtered or unexported fields
}
func NewAuditService ¶
func NewAuditService(db database.Database) *AuditService
func (*AuditService) GetAuditLogs ¶
func (s *AuditService) GetAuditLogs(limit, offset int, filters map[string]interface{}) ([]database.AuditLog, error)
GetAuditLogs retrieves audit logs with optional filters
func (*AuditService) LogAdminAction ¶
func (s *AuditService) LogAdminAction( adminUserID int, adminEmail string, operationType string, targetUserID int, targetUserEmail string, details map[string]interface{}, ipAddress string, result string, errorMessage string, ) error
LogAdminAction creates an audit log entry for an admin action
func (*AuditService) LogFailure ¶
func (s *AuditService) LogFailure( adminUserID int, adminEmail string, operationType string, targetUserID int, targetUserEmail string, details map[string]interface{}, ipAddress string, errorMessage string, ) error
LogFailure is a convenience method for logging failed operations
type CheckoutSessionRequest ¶
type CheckoutSessionResponse ¶
type DomainRateLimiter ¶
type DomainRateLimiter struct {
// contains filtered or unexported fields
}
DomainRateLimiter provides per-domain rate limiting to prevent DDoS attacks on feed servers
func NewDomainRateLimiter ¶
func NewDomainRateLimiter(config RateLimiterConfig) *DomainRateLimiter
NewDomainRateLimiter creates a new domain-based rate limiter
func (*DomainRateLimiter) Allow ¶
func (d *DomainRateLimiter) Allow(feedURL string) bool
Allow checks if a request to the given URL is allowed under rate limiting rules
func (*DomainRateLimiter) CleanupOldLimiters ¶
func (d *DomainRateLimiter) CleanupOldLimiters()
CleanupOldLimiters removes limiters that haven't been used recently to prevent memory leaks
func (*DomainRateLimiter) GetDomainStats ¶
func (d *DomainRateLimiter) GetDomainStats() map[string]DomainStats
GetDomainStats returns current statistics for all domains
func (*DomainRateLimiter) Wait ¶
func (d *DomainRateLimiter) Wait(feedURL string) error
Wait blocks until a request to the given URL is allowed, or returns an error if context is cancelled
type DomainStats ¶
type DomainStats struct {
Domain string `json:"domain"`
TokensRemaining int `json:"tokens_remaining"`
BurstLimit int `json:"burst_limit"`
}
DomainStats holds statistics for a domain's rate limiting
type ErrorDetails ¶
type ErrorDetails struct {
ErrorCode string `json:"error_code"`
Message string `json:"error"`
Details string `json:"details,omitempty"`
}
ErrorDetails provides structured error information for API responses
func GetErrorDetails ¶
func GetErrorDetails(err error) ErrorDetails
GetErrorDetails extracts user-friendly error information from an error
type FeedData ¶
type FeedData struct {
Title string
Description string
Articles []ArticleData
}
Unified feed data structure
type FeedDiscovery ¶
type FeedDiscovery struct {
// contains filtered or unexported fields
}
FeedDiscovery handles URL normalization and feed discovery
func NewFeedDiscovery ¶
func NewFeedDiscovery() *FeedDiscovery
func (*FeedDiscovery) DiscoverFeedURL ¶
func (fd *FeedDiscovery) DiscoverFeedURL(inputURL string) ([]string, error)
DiscoverFeedURL attempts to find feed URLs from a given URL
func (*FeedDiscovery) NormalizeURL ¶
func (fd *FeedDiscovery) NormalizeURL(rawURL string) (string, error)
NormalizeURL adds protocol if missing and validates the URL with SSRF protection
type FeedScheduler ¶
type FeedScheduler struct {
// contains filtered or unexported fields
}
FeedScheduler manages staggered feed updates to prevent DDoS attacks
func NewFeedScheduler ¶
func NewFeedScheduler(feedService *FeedService, rateLimiter *DomainRateLimiter, config SchedulerConfig) *FeedScheduler
NewFeedScheduler creates a new feed scheduler
func (*FeedScheduler) GetSchedulerStatus ¶
func (fs *FeedScheduler) GetSchedulerStatus() SchedulerStatus
GetSchedulerStatus returns the current status of the scheduler
func (*FeedScheduler) RefreshFeedsStaggered ¶
func (fs *FeedScheduler) RefreshFeedsStaggered() error
RefreshFeedsStaggered performs a staggered refresh of all feeds
func (*FeedScheduler) Start ¶
func (fs *FeedScheduler) Start() error
Start begins the staggered feed update process
type FeedService ¶
type FeedService struct {
// contains filtered or unexported fields
}
func NewFeedService ¶
func NewFeedService(db database.Database, rateLimiter *DomainRateLimiter) *FeedService
func (*FeedService) AddFeedForUser ¶
func (*FeedService) DeleteFeed ¶
func (fs *FeedService) DeleteFeed(id int) error
func (*FeedService) ExportOPML ¶
func (fs *FeedService) ExportOPML(userID int) ([]byte, error)
ExportOPML generates an OPML XML document containing all of a user's feed subscriptions
func (*FeedService) FindArticleByURL ¶
func (fs *FeedService) FindArticleByURL(url string) (*database.Article, error)
FindArticleByURL searches for an article by its URL across all feeds
func (*FeedService) GetArticles ¶
func (fs *FeedService) GetArticles(feedID int) ([]database.Article, error)
func (*FeedService) GetUserArticles ¶
func (fs *FeedService) GetUserArticles(userID int) ([]database.Article, error)
func (*FeedService) GetUserArticlesPaginated ¶
func (fs *FeedService) GetUserArticlesPaginated(userID int, limit int, cursor string, unreadOnly bool) (*database.ArticlePaginationResult, error)
func (*FeedService) GetUserFeedArticles ¶
func (fs *FeedService) GetUserFeedArticles(userID, feedID int) ([]database.Article, error)
func (*FeedService) GetUserFeeds ¶
func (fs *FeedService) GetUserFeeds(userID int) ([]database.Feed, error)
func (*FeedService) GetUserUnreadCounts ¶
func (*FeedService) ImportOPML ¶
func (fs *FeedService) ImportOPML(userID int, opmlData []byte) (int, error)
func (*FeedService) ImportOPMLWithLimits ¶
func (fs *FeedService) ImportOPMLWithLimits(userID int, opmlData []byte, subscriptionService *SubscriptionService) (int, error)
ImportOPMLWithLimits imports OPML feeds while respecting subscription limits
func (*FeedService) MarkAllArticlesRead ¶
func (fs *FeedService) MarkAllArticlesRead(userID int) (int, error)
func (*FeedService) MarkUserArticleRead ¶
func (fs *FeedService) MarkUserArticleRead(userID, articleID int, isRead bool) error
func (*FeedService) RefreshFeeds ¶
func (fs *FeedService) RefreshFeeds() error
func (*FeedService) SetHTTPClient ¶
func (fs *FeedService) SetHTTPClient(client HTTPClient)
SetHTTPClient sets a custom HTTP client for testing purposes
func (*FeedService) ToggleUserArticleStar ¶
func (fs *FeedService) ToggleUserArticleStar(userID, articleID int) error
func (*FeedService) UnsubscribeUserFromFeed ¶
func (fs *FeedService) UnsubscribeUserFromFeed(userID, feedID int) error
func (*FeedService) UpdateUserMaxArticlesOnFeedAdd ¶
func (fs *FeedService) UpdateUserMaxArticlesOnFeedAdd(userID, maxArticles int) error
type HTTPClient ¶
HTTPClient interface for making HTTP requests (allows mocking in tests)
type OPML ¶
type OPML struct {
XMLName xml.Name `xml:"opml"`
Head OPMLHead `xml:"head"`
Body OPMLBody `xml:"body"`
}
OPML structures for parsing OPML files
type OPMLBody ¶
type OPMLBody struct {
Outlines []OPMLOutline `xml:"outline"`
}
type OPMLOutline ¶
type PaymentService ¶
type PaymentService struct {
// contains filtered or unexported fields
}
func NewPaymentService ¶
func NewPaymentService(db database.Database, subscriptionService *SubscriptionService) *PaymentService
func (*PaymentService) CreateCheckoutSession ¶
func (ps *PaymentService) CreateCheckoutSession(req CheckoutSessionRequest) (*CheckoutSessionResponse, error)
CreateCheckoutSession creates a Stripe Checkout session for subscription
func (*PaymentService) CreateCustomerPortalSession ¶
func (ps *PaymentService) CreateCustomerPortalSession(userID int, returnURL string) (string, error)
CreateCustomerPortalSession creates a session for customer to manage their subscription
func (*PaymentService) CreateProductAndPrice ¶
func (ps *PaymentService) CreateProductAndPrice() (*stripe.Price, error)
CreateProductAndPrice creates the GoRead2 Pro product and price in Stripe (one-time setup)
func (*PaymentService) GetStripePublishableKey ¶
func (ps *PaymentService) GetStripePublishableKey() string
GetStripePublishableKey returns the Stripe publishable key for frontend
func (*PaymentService) GetStripeWebhookSecret ¶
func (ps *PaymentService) GetStripeWebhookSecret() string
GetStripeWebhookSecret returns the Stripe webhook secret for verification
func (*PaymentService) HandleSubscriptionUpdate ¶
func (ps *PaymentService) HandleSubscriptionUpdate(subscriptionID string) error
HandleSubscriptionUpdate handles subscription status changes from webhooks
func (*PaymentService) ValidateStripeConfig ¶
func (ps *PaymentService) ValidateStripeConfig() error
ValidateStripeConfig validates that all required Stripe credentials are set
type RDF ¶
type RDF struct {
XMLName xml.Name `xml:"RDF"`
Channel RDFChannel `xml:"channel"`
Items []RDFItem `xml:"item"`
}
type RDFChannel ¶
type RateLimitError ¶
RateLimitError represents a rate limiting error
func (*RateLimitError) Error ¶
func (e *RateLimitError) Error() string
type RateLimiterConfig ¶
type RateLimiterConfig struct {
RequestsPerMinute int // Maximum requests per minute per domain
BurstSize int // Burst allowance for each domain
}
RateLimiterConfig holds configuration for the rate limiter
type ScheduledFeed ¶
type ScheduledFeed struct {
Feed database.Feed
NextUpdate time.Time
Priority int // Higher number = higher priority
}
ScheduledFeed represents a feed scheduled for update
type SchedulerConfig ¶
type SchedulerConfig struct {
UpdateWindow time.Duration // Default: 6 hours
MinInterval time.Duration // Default: 30 minutes
MaxConcurrent int // Default: 10
CleanupInterval time.Duration // Default: 1 hour
}
SchedulerConfig holds configuration for the feed scheduler
type SchedulerStatus ¶
type SchedulerStatus struct {
IsRunning bool `json:"is_running"`
UpdateWindow time.Duration `json:"update_window"`
MinInterval time.Duration `json:"min_interval"`
MaxConcurrent int `json:"max_concurrent"`
CleanupInterval time.Duration `json:"cleanup_interval"`
}
SchedulerStatus holds the current status of the scheduler
type SubscriptionInfo ¶
type SubscriptionInfo struct {
Status string `json:"status"`
SubscriptionID string `json:"subscription_id"`
TrialEndsAt time.Time `json:"trial_ends_at"`
LastPaymentDate time.Time `json:"last_payment_date"`
NextBillingDate *time.Time `json:"next_billing_date,omitempty"`
CurrentFeeds int `json:"current_feeds"`
FeedLimit int `json:"feed_limit"` // -1 for unlimited
CanAddFeeds bool `json:"can_add_feeds"`
IsActive bool `json:"is_active"`
TrialDaysRemaining int `json:"trial_days_remaining,omitempty"`
}
SubscriptionInfo contains all subscription-related information for a user
type SubscriptionService ¶
type SubscriptionService struct {
// contains filtered or unexported fields
}
func NewSubscriptionService ¶
func NewSubscriptionService(db database.Database) *SubscriptionService
func (*SubscriptionService) CanUserAddFeed ¶
func (ss *SubscriptionService) CanUserAddFeed(userID int) error
CanUserAddFeed checks if a user can add another feed based on their subscription status
func (*SubscriptionService) GenerateAdminToken ¶
func (ss *SubscriptionService) GenerateAdminToken(description string) (string, error)
GenerateAdminToken creates a new cryptographically secure admin token
func (*SubscriptionService) GetDB ¶
func (ss *SubscriptionService) GetDB() database.Database
GetDB returns the database instance (for admin commands)
func (*SubscriptionService) GetUserByEmail ¶
func (ss *SubscriptionService) GetUserByEmail(email string) (*database.User, error)
func (*SubscriptionService) GetUserSubscriptionInfo ¶
func (ss *SubscriptionService) GetUserSubscriptionInfo(userID int) (*SubscriptionInfo, error)
GetUserSubscriptionInfo returns subscription information for the user
func (*SubscriptionService) GrantFreeMonths ¶
func (ss *SubscriptionService) GrantFreeMonths(userID int, months int) error
func (*SubscriptionService) HasAdminTokens ¶
func (ss *SubscriptionService) HasAdminTokens() (bool, error)
HasAdminTokens checks if any active admin tokens exist
func (*SubscriptionService) ListAdminTokens ¶
func (ss *SubscriptionService) ListAdminTokens() ([]AdminToken, error)
ListAdminTokens returns all admin tokens (without the actual token values)
func (*SubscriptionService) RevokeAdminToken ¶
func (ss *SubscriptionService) RevokeAdminToken(tokenID int) error
RevokeAdminToken deactivates an admin token
func (*SubscriptionService) SetUserAdmin ¶
func (ss *SubscriptionService) SetUserAdmin(userID int, isAdmin bool) error
Admin management methods
func (*SubscriptionService) UpdateUserSubscription ¶
func (ss *SubscriptionService) UpdateUserSubscription(userID int, status, subscriptionID string, lastPaymentDate, nextBillingDate time.Time) error
UpdateUserSubscription updates a user's subscription status
func (*SubscriptionService) ValidateAdminToken ¶
func (ss *SubscriptionService) ValidateAdminToken(token string) (bool, error)
ValidateAdminToken checks if a token is valid and updates last_used_at
type URLValidator ¶
type URLValidator struct {
// AllowedSchemes restricts the URL schemes that can be used
AllowedSchemes map[string]bool
// BlockedNetworks contains IP ranges that should not be accessed
BlockedNetworks []*net.IPNet
}
URLValidator provides SSRF protection for feed URL validation
func NewURLValidator ¶
func NewURLValidator() *URLValidator
NewURLValidator creates a new URL validator with secure defaults
func (*URLValidator) CreateSecureHTTPClient ¶
func (v *URLValidator) CreateSecureHTTPClient(timeout time.Duration) *http.Client
CreateSecureHTTPClient creates an HTTP client with SSRF protection
func (*URLValidator) ValidateAndNormalizeURL ¶
func (v *URLValidator) ValidateAndNormalizeURL(rawURL string) (string, error)
ValidateAndNormalizeURL validates a URL and normalizes it (adds https:// if needed)
func (*URLValidator) ValidateURL ¶
func (v *URLValidator) ValidateURL(rawURL string) error
ValidateURL validates a URL for SSRF protection