hashtags

package
v1.1.17 Latest Latest
Warning

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

Go to latest
Published: Feb 21, 2026 License: AGPL-3.0 Imports: 9 Imported by: 0

README

Hashtags Service

The Hashtags Service provides comprehensive hashtag management and discovery functionality for the Lesser ActivityPub server.

Overview

This service implements hashtag following, timeline queries, suggestions, and real-time activity subscriptions, enabling users to discover and engage with content based on their interests.

Features

Core Operations
  • Follow/Unfollow Hashtags: Users can follow hashtags to receive updates
  • Hashtag Timelines: Single and multi-hashtag timeline queries with union (ANY) and intersection (ALL) modes
  • Hashtag Statistics: Track usage counts, trending scores, and historical data
  • Hashtag Discovery: Get suggested hashtags based on trending topics and user activity
  • Notification Management: Customize notification settings for followed hashtags
  • Hashtag Muting: Temporarily or permanently mute hashtags
Real-time Features
  • Activity Subscriptions: Stream real-time posts for followed hashtags
  • Trending Updates: Track trending hashtags and their activity

Architecture

Service Layer (service.go)

The service implements the CQRS pattern with distinct Command and Query types:

Commands (mutations):

  • FollowHashtagCommand
  • UnfollowHashtagCommand
  • UpdateHashtagNotificationsCommand
  • MuteHashtagCommand

Queries (reads):

  • GetHashtagQuery
  • GetFollowedHashtagsQuery
  • GetHashtagTimelineQuery
  • GetMultiHashtagTimelineQuery
  • GetSuggestedHashtagsQuery
Storage Layer

The service uses:

  • HashtagRepository: For hashtag metadata and relationships
  • StatusRepository: For timeline queries
  • RelationshipRepository: For user context
Integration

The service is registered in the services.Registry and accessed via:

hashtagService := registry.Hashtags()

GraphQL API

Queries
# Get hashtag information
hashtag(name: String!): Hashtag

# Get followed hashtags
followedHashtags(first: Int, after: String): HashtagConnection!

# Get single hashtag timeline
hashtagTimeline(hashtag: String!, first: Int, after: String): PostConnection!

# Get multi-hashtag timeline
multiHashtagTimeline(
  hashtags: [String!]!
  mode: HashtagMode!
  first: Int
  after: String
): PostConnection!

# Get suggested hashtags
suggestedHashtags(limit: Int): [HashtagSuggestion!]!
Mutations
# Follow a hashtag
followHashtag(hashtag: String!, notifyLevel: NotificationLevel): HashtagFollowPayload!

# Unfollow a hashtag
unfollowHashtag(hashtag: String!): UnfollowHashtagPayload!

# Update notification settings
updateHashtagNotifications(
  hashtag: String!
  settings: HashtagNotificationSettingsInput!
): UpdateHashtagNotificationsPayload!

# Mute a hashtag
muteHashtag(hashtag: String!, until: Time): MuteHashtagPayload!
Subscriptions
# Stream hashtag activity
hashtagActivity(hashtags: [String!]!): HashtagActivityUpdate!

Usage Examples

Follow a Hashtag
result, err := hashtagService.FollowHashtag(ctx, &hashtags.FollowHashtagCommand{
    UserID:               "alice",
    Hashtag:              "golang",
    NotificationsEnabled: true,
})
Get Hashtag Timeline
result, err := hashtagService.GetHashtagTimeline(ctx, &hashtags.GetHashtagTimelineQuery{
    Hashtag:  "golang",
    First:    20,
    ViewerID: "alice",
})
Get Multi-Hashtag Timeline
// Union mode (ANY): Posts with any of the hashtags
result, err := hashtagService.GetMultiHashtagTimeline(ctx, &hashtags.GetMultiHashtagTimelineQuery{
    Hashtags: []string{"golang", "rust", "typescript"},
    Mode:     "ANY",
    First:    20,
    ViewerID: "alice",
})

// Intersection mode (ALL): Posts with all hashtags
result, err := hashtagService.GetMultiHashtagTimeline(ctx, &hashtags.GetMultiHashtagTimelineQuery{
    Hashtags: []string{"golang", "webdev"},
    Mode:     "ALL",
    First:    20,
    ViewerID: "alice",
})
Get Suggested Hashtags
suggestions, err := hashtagService.GetSuggestedHashtags(ctx, &hashtags.GetSuggestedHashtagsQuery{
    UserID: "alice",
    Limit:  10,
})

Error Handling

The service defines custom error types in errors.go:

  • ErrGetHashtag: Failed to retrieve hashtag information
  • ErrFollowHashtag: Failed to follow hashtag
  • ErrUnfollowHashtag: Failed to unfollow hashtag
  • ErrGetHashtagTimeline: Failed to retrieve timeline
  • ErrHashtagNameRequired: Validation error for missing hashtag name
  • And more...

Testing

Unit tests are provided in service_test.go. Run tests with:

go test ./pkg/services/hashtags/...

Performance Considerations

  • Timeline Queries: Use cursor-based pagination for efficient large result sets
  • Multi-Hashtag Queries: Intersection mode (ALL) is more expensive than union mode (ANY)
  • Trending Calculations: Cached and updated periodically
  • Subscriptions: Buffer size of 100 events prevents blocking

Future Enhancements

  • Advanced hashtag suggestions based on user interests and social graph
  • Hashtag recommendations using machine learning
  • Enhanced analytics and insights
  • Hashtag groups/bundles for related topics
  • Hashtag moderation and quality controls
  • Storage Models: pkg/storage/models/hashtag*.go
  • Repository: pkg/storage/repositories/hashtag_repository.go
  • GraphQL Resolvers: graph/schema.resolvers.go (hashtag section)
  • GraphQL Schema: graph/*.graphql (sources) / docs/contracts/graphql-schema.graphql (published)

Dependencies

  • pkg/storage/repositories: Data access layer
  • pkg/streaming: Real-time event publishing
  • go.uber.org/zap: Structured logging
  • pkg/common: Validation utilities

Contributing

When adding new features:

  1. Add command/query types in service.go
  2. Implement service methods
  3. Add corresponding error types in errors.go
  4. Add GraphQL resolvers in graph/schema.resolvers.go
  5. Add unit tests in service_test.go
  6. Update this README

License

Part of the Lesser ActivityPub server project.

Documentation

Overview

Package hashtags implements the hashtag follow/mute business logic used by GraphQL resolvers.

Index

Constants

This section is empty.

Variables

View Source
var (
	// Query errors
	ErrGetHashtag              = errors.New("failed to get hashtag")
	ErrGetFollowedHashtags     = errors.New("failed to get followed hashtags")
	ErrGetHashtagTimeline      = errors.New("failed to get hashtag timeline")
	ErrGetMultiHashtagTimeline = errors.New("failed to get multi-hashtag timeline")
	ErrGetHashtagStats         = errors.New("failed to get hashtag statistics")
	ErrCheckFollowingHashtag   = errors.New("failed to check if following hashtag")

	// Mutation errors
	ErrFollowHashtag              = errors.New("failed to follow hashtag")
	ErrUnfollowHashtag            = errors.New("failed to unfollow hashtag")
	ErrUpdateHashtagNotifications = errors.New("failed to update hashtag notification settings")
	ErrMuteHashtag                = errors.New("failed to mute hashtag")
	ErrUnmuteHashtag              = errors.New("failed to unmute hashtag")

	// Validation errors
	ErrHashtagNameRequired  = errors.New("hashtag name is required")
	ErrHashtagNameTooLong   = errors.New("hashtag name is too long")
	ErrInvalidHashtagFormat = errors.New("invalid hashtag format")
	ErrInvalidMode          = errors.New("invalid timeline mode, must be ANY or ALL")

	// Resource errors
	ErrHashtagNotFound        = errors.New("hashtag not found")
	ErrHashtagAlreadyFollowed = errors.New("hashtag is already followed")
	ErrHashtagNotFollowed     = errors.New("hashtag is not followed")

	// Infrastructure errors
	ErrPublisherNotAvailable = errors.New("publisher not available for hashtag events")
)

Error variables for hashtag service operations

Functions

This section is empty.

Types

type ActivityEvent

type ActivityEvent struct {
	Hashtag   string
	StatusID  string
	ActorID   string
	Timestamp time.Time
	Event     *streaming.InternalEvent
}

ActivityEvent represents a hashtag-related streaming event forwarded from the global bus.

type Hashtag

type Hashtag struct {
	Name                 string
	URL                  string
	PostCount            int
	FollowerCount        int
	TrendingScore        float64
	Related              []string
	IsFollowing          bool
	IsMuted              bool
	FollowedAt           *time.Time
	NotificationSettings *storage.HashtagNotificationSettings
	Stats                *storage.HashtagStats
	CreatedAt            time.Time
	UpdatedAt            time.Time
}

Hashtag captures the service-level representation of a hashtag enriched with viewer state.

type HashtagRepository

type HashtagRepository interface {
	FollowHashtag(ctx context.Context, userID, hashtag string) error
	UnfollowHashtag(ctx context.Context, userID, hashtag string) error
	IsFollowingHashtag(ctx context.Context, userID, hashtag string) (bool, error)
	GetFollowedHashtags(ctx context.Context, userID string, limit int, cursor string) ([]*storage.HashtagFollow, string, error)
	GetHashtagInfo(ctx context.Context, hashtag string) (*storage.Hashtag, error)
	GetHashtagStats(ctx context.Context, hashtag string) (any, error)
	GetHashtagTimelineAdvanced(ctx context.Context, hashtag string, maxID *string, limit int, visibility string) ([]*storage.StatusSearchResult, error)
	UpdateHashtagNotificationSettings(ctx context.Context, userID, hashtag string, settings *storage.HashtagNotificationSettings) error
	MuteHashtag(ctx context.Context, userID, hashtag string, until *time.Time) error
	IsHashtagMuted(ctx context.Context, userID, hashtag string) (bool, error)
	GetHashtagNotificationSettings(ctx context.Context, userID, hashtag string) (*storage.HashtagNotificationSettings, error)
}

HashtagRepository defines the storage interface needed by the hashtag service.

type Service

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

Service coordinates hashtag follow/mute state with storage repositories and the streaming layer.

func NewService

func NewService(
	hashtagRepo HashtagRepository,
	accountRepo interfaces.AccountRepository,
	objectRepo interfaces.ObjectRepository,
	publisher streaming.Publisher,
	logger *zap.Logger,
) *Service

NewService wires repositories and infrastructure needed for the hashtag service.

func (*Service) FollowHashtag

func (s *Service) FollowHashtag(ctx context.Context, userID, hashtag string, settings *storage.HashtagNotificationSettings) (*Hashtag, error)

FollowHashtag creates a follow record, optionally updates notification settings, and emits streaming events.

func (*Service) GetFollowedHashtags

func (s *Service) GetFollowedHashtags(
	ctx context.Context,
	userID string,
	pagination *interfaces.PaginationOptions,
) ([]*Hashtag, string, error)

GetFollowedHashtags returns the viewer's followed hashtags enriched with current metadata.

func (*Service) GetHashtag

func (s *Service) GetHashtag(ctx context.Context, name, viewerID string) (*Hashtag, error)

GetHashtag loads the latest hashtag metadata plus viewer specific state.

func (*Service) GetHashtagActivity

func (s *Service) GetHashtagActivity(ctx context.Context, hashtags []string) (<-chan *ActivityEvent, error)

GetHashtagActivity subscribes to hashtag-related events via DynamoDB-backed streaming. DEPRECATED: This method uses in-memory EventBus pattern that doesn't work on Lambda. Clients should use GraphQL subscriptions (SubscribeToHashtagActivity) instead, which properly persists subscriptions in DynamoDB and delivers via stream-router.

func (*Service) MuteHashtag

func (s *Service) MuteHashtag(ctx context.Context, userID, hashtag string, until *time.Time) (*Hashtag, error)

MuteHashtag persists a mute entry for the user and broadcasts the change.

func (*Service) UnfollowHashtag

func (s *Service) UnfollowHashtag(ctx context.Context, userID, hashtag string) (*Hashtag, error)

UnfollowHashtag deletes a follow record and notifies streaming consumers.

Jump to

Keyboard shortcuts

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