services

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 11, 2025 License: MIT Imports: 36 Imported by: 0

Documentation

Index

Constants

View Source
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

View Source
const (
	FreeTrialFeedLimit = 20
)

Variables

View Source
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

View Source
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 ArticleData struct {
	Title       string
	Link        string
	Description string
	Content     string
	Author      string
	PublishedAt time.Time
}

type Atom

type Atom struct {
	XMLName  xml.Name    `xml:"feed"`
	Title    string      `xml:"title"`
	Subtitle string      `xml:"subtitle"`
	Entries  []AtomEntry `xml:"entry"`
}

type AtomAuthor

type AtomAuthor struct {
	Name string `xml:"name"`
}

type AtomContent

type AtomContent struct {
	Type    string `xml:"type,attr"`
	Content string `xml:",chardata"`
}

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 AtomLink struct {
	Href string `xml:"href,attr"`
}

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

func (*AuditService) LogSuccess

func (s *AuditService) LogSuccess(
	adminUserID int,
	adminEmail string,
	operationType string,
	targetUserID int,
	targetUserEmail string,
	details map[string]interface{},
	ipAddress string,
) error

LogSuccess is a convenience method for logging successful operations

type Channel

type Channel struct {
	Title       string `xml:"title"`
	Description string `xml:"description"`
	Items       []Item `xml:"item"`
}

type CheckoutSessionRequest

type CheckoutSessionRequest struct {
	UserID     int    `json:"user_id"`
	SuccessURL string `json:"success_url"`
	CancelURL  string `json:"cancel_url"`
}

type CheckoutSessionResponse

type CheckoutSessionResponse struct {
	SessionID  string `json:"session_id"`
	SessionURL string `json:"session_url"`
}

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

func (*FeedScheduler) Stop

func (fs *FeedScheduler) Stop()

Stop stops the feed scheduler

type FeedService

type FeedService struct {
	// contains filtered or unexported fields
}

func NewFeedService

func NewFeedService(db database.Database, rateLimiter *DomainRateLimiter) *FeedService

func (*FeedService) AddFeed

func (fs *FeedService) AddFeed(url string) (*database.Feed, error)

func (*FeedService) AddFeedForUser

func (fs *FeedService) AddFeedForUser(userID int, inputURL string) (*database.Feed, error)

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) GetFeeds

func (fs *FeedService) GetFeeds() ([]database.Feed, 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 (fs *FeedService) GetUserUnreadCounts(userID int, userFeeds []database.Feed) (map[int]int, error)

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

type HTTPClient interface {
	Do(req *http.Request) (*http.Response, error)
}

HTTPClient interface for making HTTP requests (allows mocking in tests)

type Item

type Item struct {
	Title       string `xml:"title"`
	Link        string `xml:"link"`
	Description string `xml:"description"`
	Author      string `xml:"author"`
	PubDate     string `xml:"pubDate"`
	Content     string `xml:"encoded"`
}

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 OPMLHead

type OPMLHead struct {
	Title string `xml:"title"`
}

type OPMLOutline

type OPMLOutline struct {
	Text    string        `xml:"text,attr"`
	Title   string        `xml:"title,attr"`
	Type    string        `xml:"type,attr"`
	XMLURL  string        `xml:"xmlUrl,attr"`
	HTMLURL string        `xml:"htmlUrl,attr"`
	Outline []OPMLOutline `xml:"outline"`
}

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 RDFChannel struct {
	Title       string `xml:"title"`
	Description string `xml:"description"`
	Link        string `xml:"link"`
}

type RDFItem

type RDFItem struct {
	Title       string `xml:"title"`
	Link        string `xml:"link"`
	Description string `xml:"description"`
	Date        string `xml:"date"`
	Creator     string `xml:"creator"`
}

type RSS

type RSS struct {
	XMLName xml.Name `xml:"rss"`
	Channel Channel  `xml:"channel"`
}

type RateLimitError

type RateLimitError struct {
	Domain  string
	Message string
}

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

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

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL