Run the modernize linter across the codebase. Change generated by running: ``` go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./... ``` Ref: https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize
		
			
				
	
	
		
			169 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			169 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package services
 | 
						|
 | 
						|
import (
 | 
						|
	"CatsOfMastodonBotGo/internal/config"
 | 
						|
	"CatsOfMastodonBotGo/internal/domain"
 | 
						|
	"context"
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"math/rand"
 | 
						|
	"net/http"
 | 
						|
	"slices"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"gorm.io/gorm"
 | 
						|
	"gorm.io/gorm/clause"
 | 
						|
)
 | 
						|
 | 
						|
type PostService struct {
 | 
						|
	db  *gorm.DB
 | 
						|
	cfg *config.Config
 | 
						|
}
 | 
						|
 | 
						|
// Constructor
 | 
						|
func NewPostService(db *gorm.DB, cfg *config.Config) *PostService {
 | 
						|
	return &PostService{db: db, cfg: cfg}
 | 
						|
}
 | 
						|
 | 
						|
func (ps *PostService) GetPostsFromApi(ctx context.Context, tag string, instance string) ([]domain.Post, error) {
 | 
						|
	var requestUrl = instance + "/api/v1/timelines/tag/" + tag + "?limit=40"
 | 
						|
	req, err := http.NewRequestWithContext(ctx, "GET", requestUrl, nil)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	resp, err := http.DefaultClient.Do(req)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if resp.StatusCode != 200 || strings.Split(strings.ToLower(resp.Header.Get("Content-Type")), ";")[0] != "application/json" {
 | 
						|
		return nil, fmt.Errorf("status code: %d, content-type: %s", resp.StatusCode, resp.Header.Get("Content-Type"))
 | 
						|
	}
 | 
						|
 | 
						|
	var posts []domain.Post = nil
 | 
						|
	err = json.NewDecoder(resp.Body).Decode(&posts)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	// defer: it basically means "do this later when the function returns"
 | 
						|
	defer resp.Body.Close()
 | 
						|
	if posts == nil {
 | 
						|
		return nil, fmt.Errorf("no posts found for tag %s on instance %s", tag, instance)
 | 
						|
	}
 | 
						|
	return posts, nil
 | 
						|
}
 | 
						|
 | 
						|
func (ps *PostService) GetExistingPostIds() []string {
 | 
						|
	var existingPostIds []string
 | 
						|
	ps.db.Model(&domain.Post{}).Pluck("id", &existingPostIds)
 | 
						|
	return existingPostIds
 | 
						|
}
 | 
						|
 | 
						|
func (ps *PostService) GetExistingAccountIds() []string {
 | 
						|
	var existingAccountIds []string
 | 
						|
	ps.db.Model(&domain.Account{}).Pluck("acc_id", &existingAccountIds)
 | 
						|
	return existingAccountIds
 | 
						|
}
 | 
						|
 | 
						|
func (ps *PostService) GetNewPosts(existingPostIds []string, posts []domain.Post) []domain.Post {
 | 
						|
	var newPosts []domain.Post = nil
 | 
						|
	for _, post := range posts {
 | 
						|
		if !arrayContains(existingPostIds, post.ID) && len(post.Attachments) > 0 && !post.Account.IsBot {
 | 
						|
			var allImageMedia = true
 | 
						|
			for _, attachment := range post.Attachments {
 | 
						|
				if attachment.Type != "image" {
 | 
						|
					allImageMedia = false
 | 
						|
					break
 | 
						|
				}
 | 
						|
			} // Inefficient but anyways
 | 
						|
			if allImageMedia {
 | 
						|
				newPosts = append(newPosts, post)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return newPosts
 | 
						|
}
 | 
						|
 | 
						|
func (ps *PostService) GetNewAccounts(existingAccountIds []string, posts []domain.Post) []domain.Account {
 | 
						|
	var newAccounts []domain.Account = nil
 | 
						|
	for _, post := range posts {
 | 
						|
		if !arrayContains(existingAccountIds, post.Account.AccId) {
 | 
						|
			newAccounts = append(newAccounts, post.Account)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return newAccounts
 | 
						|
}
 | 
						|
 | 
						|
func (ps *PostService) InsertNewPosts(newPosts []domain.Post) int {
 | 
						|
	return int(ps.db.Create(&newPosts).RowsAffected)
 | 
						|
}
 | 
						|
 | 
						|
func (ps *PostService) InsertNewAccounts(newAccounts []domain.Account) int {
 | 
						|
	return int(ps.db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&newAccounts).RowsAffected)
 | 
						|
}
 | 
						|
 | 
						|
// From this point on, its for the api endpoints
 | 
						|
 | 
						|
func (ps *PostService) GetRandomPost() domain.Post {
 | 
						|
	var post domain.Post
 | 
						|
	var postIDs []uint
 | 
						|
 | 
						|
	// AI Enhanced
 | 
						|
	// Step 1: Fetch eligible post IDs (with at least one approved attachment)
 | 
						|
	ps.db.
 | 
						|
		Model(&domain.Post{}).
 | 
						|
		Where("exists (select 1 from media_attachments where post_id = posts.id and approved = ?)", true).
 | 
						|
		Pluck("id", &postIDs)
 | 
						|
 | 
						|
	if len(postIDs) == 0 {
 | 
						|
		return domain.Post{} // No eligible posts
 | 
						|
	}
 | 
						|
 | 
						|
	// Step 2: Pick a random ID in Go
 | 
						|
	randomID := postIDs[rand.Intn(len(postIDs))]
 | 
						|
 | 
						|
	// Step 3: Load the full post with related data
 | 
						|
	ps.db.
 | 
						|
		Preload("Account").
 | 
						|
		Preload("Attachments", "Approved = ?", true).
 | 
						|
		First(&post, randomID)
 | 
						|
 | 
						|
	// Step 4: Keep only the first attachment if any
 | 
						|
	if len(post.Attachments) > 0 {
 | 
						|
		post.Attachments = []domain.MediaAttachment{post.Attachments[0]}
 | 
						|
	}
 | 
						|
 | 
						|
	return post
 | 
						|
}
 | 
						|
 | 
						|
func (ps *PostService) ApproveMedia(mediaId string) bool {
 | 
						|
	return ps.db.Model(&domain.MediaAttachment{}).
 | 
						|
		Where("id = ?", mediaId).
 | 
						|
		Update("approved", true).RowsAffected > 0
 | 
						|
}
 | 
						|
 | 
						|
func (ps *PostService) RejectMedia(mediaId string) bool {
 | 
						|
	return ps.db.Model(&domain.MediaAttachment{}).
 | 
						|
		Where("id = ?", mediaId).
 | 
						|
		Update("rejected", true).RowsAffected > 0
 | 
						|
}
 | 
						|
 | 
						|
// Get a post which approve and rejet are false (For admin panel)
 | 
						|
func (ps *PostService) GetMedia() domain.MediaAttachment {
 | 
						|
	var media domain.MediaAttachment
 | 
						|
	orderExpr := "RANDOM()" // sqlite
 | 
						|
	if ps.cfg.DBEngine != "sqlite" {
 | 
						|
		orderExpr = "RAND()" // mariadb/mysql
 | 
						|
	}
 | 
						|
	ps.db.Model(&domain.MediaAttachment{}).
 | 
						|
		Where("approved = ?", false).
 | 
						|
		Where("rejected = ?", false).
 | 
						|
		Order(orderExpr).
 | 
						|
		First(&media)
 | 
						|
	return media
 | 
						|
}
 | 
						|
 | 
						|
func arrayContains(arr []string, str string) bool {
 | 
						|
	return slices.Contains(arr, str)
 | 
						|
}
 |