package postgres import ( "context" "errors" "time" otterError "git.dragse.it/anthrove/otter-space-sdk/pkg/error" "git.dragse.it/anthrove/otter-space-sdk/pkg/models" gonanoid "github.com/matoous/go-nanoid/v2" log "github.com/sirupsen/logrus" "gorm.io/gorm" ) // Workaround, should be changed later maybe, but its not that bad right now type selectFrequencyTag struct { tagName string `gorm:"tag_name"` count int64 `gorm:"count"` tagType models.TagType `gorm:"tag_type"` } func CreateUser(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) error { if anthroveUserID == "" { return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty} } if len(anthroveUserID) != 25 { return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort} } user := models.User{ BaseModel: models.BaseModel[models.AnthroveUserID]{ ID: anthroveUserID, }, } result := db.WithContext(ctx).FirstOrCreate(&user) if result.Error != nil { if errors.Is(result.Error, gorm.ErrDuplicatedKey) { return &otterError.EntityAlreadyExists{} } return result.Error } return nil } func CreateUserWithRelationToSource(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, accountId string, accountUsername string) error { if anthroveUserID == "" { return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty} } if len(anthroveUserID) != 25 { return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort} } if accountId == "" { return &otterError.EntityValidationFailed{Reason: "accountID cannot be empty"} } if accountUsername == "" { return &otterError.EntityValidationFailed{Reason: "accountUsername cannot be empty"} } validationCode, err := gonanoid.New(25) if err != nil { return err } result := db.WithContext(ctx).Exec(`WITH userObj AS ( INSERT INTO "User" (id) VALUES ($1) ON CONFLICT (id) DO NOTHING ) INSERT INTO "UserSource" (user_id, source_id, account_username, account_id, account_validate, account_validation_key) SELECT $2, source.id, $3, $4, false, $5 FROM "Source" AS source WHERE source.id = $6;`, anthroveUserID, anthroveUserID, accountUsername, accountId, validationCode, sourceID) if result.Error != nil { if errors.Is(result.Error, gorm.ErrDuplicatedKey) { return &otterError.EntityAlreadyExists{} } return result.Error } if result.RowsAffected == 0 { return &otterError.NoDataWritten{} } log.WithFields(log.Fields{ "anthrove_user_id": anthroveUserID, "source_id": sourceID, "account_username": accountUsername, "account_id": accountId, }).Info("database: created user-source relationship") return nil } func GetUserFavoritesCount(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) (int64, error) { var count int64 if anthroveUserID == "" { return 0, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty} } if len(anthroveUserID) != 25 { return 0, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort} } result := db.WithContext(ctx).Model(&models.UserFavorites{}).Where("user_id = ?", string(anthroveUserID)).Count(&count) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return 0, &otterError.NoDataFound{} } return 0, result.Error } log.WithFields(log.Fields{ "anthrove_user_id": anthroveUserID, "anthrove_user_fav_count": count, }).Trace("database: got user favorite count") return count, nil } func GetUserSourceLinks(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) (map[string]models.UserSource, error) { var userSources []models.UserSource userSourceMap := make(map[string]models.UserSource) if anthroveUserID == "" { return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty} } if len(anthroveUserID) != 25 { return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort} } result := db.WithContext(ctx).Model(&models.UserSource{}).Where("user_id = ?", string(anthroveUserID)).Find(&userSources) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, &otterError.NoDataFound{} } return nil, result.Error } for _, userSource := range userSources { var source models.Source result = db.WithContext(ctx).Model(&models.Source{}).Where("id = ?", userSource.SourceID).First(&source) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, &otterError.NoDataFound{} } return nil, result.Error } userSourceMap[source.DisplayName] = models.UserSource{ UserID: userSource.AccountID, AccountUsername: userSource.AccountUsername, Source: models.Source{ DisplayName: source.DisplayName, Domain: source.Domain, Icon: source.Icon, }, } } log.WithFields(log.Fields{ "anthrove_user_id": anthroveUserID, }).Trace("database: got user source link") return userSourceMap, nil } func GetUserSourceBySourceID(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID) (*models.UserSource, error) { var userSource models.UserSource if anthroveUserID == "" { return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty} } if len(anthroveUserID) != 25 { return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort} } if sourceID == "" { return nil, &otterError.EntityValidationFailed{Reason: "sourceID cannot be empty"} } if len(sourceID) != 25 { return nil, &otterError.EntityValidationFailed{Reason: "sourceID needs to be 25 characters long"} } result := db.WithContext(ctx).Model(&models.UserSource{}).InnerJoins("Source", db.Where("id = ?", sourceID)).Where("user_id = ?", string(anthroveUserID)).First(&userSource) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, &otterError.NoDataFound{} } return nil, result.Error } log.WithFields(log.Fields{ "anthrove_user_id": anthroveUserID, "source_id": sourceID, }).Trace("database: got specified user source link") return &userSource, nil } func GetAllUsers(ctx context.Context, db *gorm.DB) ([]models.User, error) { var users []models.User result := db.WithContext(ctx).Model(&models.User{}).Find(&users) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, &otterError.NoDataFound{} } return nil, result.Error } log.WithFields(log.Fields{ "anthrove_user_id_count": len(users), }).Trace("database: got all anthrove user IDs") return users, nil } func GetUserFavoriteWithPagination(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, skip int, limit int) (*models.FavoriteList, error) { var userFavorites []models.UserFavorites var favoritePosts []models.Post if anthroveUserID == "" { return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty} } if len(anthroveUserID) != 25 { return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort} } err := db.WithContext(ctx).Model(&models.UserFavorites{}).Where("user_id = ?", string(anthroveUserID)).Offset(skip).Limit(limit).Find(&userFavorites).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, &otterError.NoDataFound{} } return nil, err } for _, userFavorite := range userFavorites { var post models.Post err = db.WithContext(ctx).Model(&models.Post{}).Where("id = ?", userFavorite.PostID).First(&post).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, &otterError.NoDataFound{} } return nil, err } favoritePosts = append(favoritePosts, models.Post{ BaseModel: models.BaseModel[models.AnthrovePostID]{ID: post.ID}, Rating: post.Rating, }) } log.WithFields(log.Fields{ "anthrove_user_id": anthroveUserID, "anthrove_user_fav_count": len(favoritePosts), }).Trace("database: got all anthrove user favorites") return &models.FavoriteList{Posts: favoritePosts}, nil } func GetUserTagWitRelationToFavedPosts(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) ([]models.TagsWithFrequency, error) { var queryUserFavorites []selectFrequencyTag if anthroveUserID == "" { return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty} } if len(anthroveUserID) != 25 { return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort} } rows, err := db.WithContext(ctx).Raw( `WITH user_posts AS ( SELECT post_id FROM "UserFavorites" WHERE user_id = $1 ) SELECT post_tags.tag_name AS tag_name, count(*) AS count, (SELECT tag_type FROM "Tag" WHERE "Tag".name = post_tags.tag_name LIMIT 1) AS tag_type FROM post_tags, user_posts WHERE post_tags.post_id IN (user_posts.post_id) GROUP BY post_tags.tag_name ORDER BY tag_type DESC, tag_name DESC`, anthroveUserID).Rows() if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, &otterError.NoDataFound{} } return nil, err } var userFavoritesFrequency = make([]models.TagsWithFrequency, 0) defer rows.Close() for rows.Next() { var tagName string var count int64 var tagType string rows.Scan(&tagName, &count, &tagType) userFavoritesFrequency = append(userFavoritesFrequency, models.TagsWithFrequency{ Frequency: count, Tags: models.Tag{ Name: tagName, Type: models.TagType(tagType), }, }) } log.WithFields(log.Fields{ "anthrove_user_id": anthroveUserID, "tag_amount": len(queryUserFavorites), }).Trace("database: got user tag node with relation to faved posts") return userFavoritesFrequency, nil } func UpdateUserSourceScrapeTimeInterval(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, scrapeTime models.AnthroveScrapeTimeInterval) error { if anthroveUserID == "" { return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty} } if len(anthroveUserID) != 25 { return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort} } if sourceID == "" { return &otterError.EntityValidationFailed{Reason: otterError.AnthroveSourceIDEmpty} } if len(sourceID) != 25 { return &otterError.EntityValidationFailed{Reason: otterError.AnthroveSourceIDToShort} } if scrapeTime == 0 { return &otterError.EntityValidationFailed{Reason: "ScrapeTimeInterval cannot be empty"} } userSource := &models.UserSource{ UserID: string(anthroveUserID), } result := db.WithContext(ctx).Model(&userSource).Update("scrape_time_interval", scrapeTime) if result.Error != nil { return result.Error } return nil } func UpdateUserSourceLastScrapeTime(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, lastScrapeTime models.AnthroveUserLastScrapeTime) error { if anthroveUserID == "" { return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty} } if len(anthroveUserID) != 25 { return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort} } if sourceID == "" { return &otterError.EntityValidationFailed{Reason: otterError.AnthroveSourceIDEmpty} } if len(sourceID) != 25 { return &otterError.EntityValidationFailed{Reason: otterError.AnthroveSourceIDToShort} } if time.Time.IsZero(time.Time(lastScrapeTime)) { return &otterError.EntityValidationFailed{Reason: "LastScrapeTime cannot be empty"} } userSource := &models.UserSource{ UserID: string(anthroveUserID), } result := db.WithContext(ctx).Model(&userSource).Update("last_scrape_time", time.Time(lastScrapeTime)) if result.Error != nil { return result.Error } return nil } func UpdateUserSourceValidation(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, valid bool) error { if anthroveUserID == "" { return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty} } if len(anthroveUserID) != 25 { return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort} } if sourceID == "" { return &otterError.EntityValidationFailed{Reason: otterError.AnthroveSourceIDEmpty} } if len(sourceID) != 25 { return &otterError.EntityValidationFailed{Reason: otterError.AnthroveSourceIDToShort} } userSource := &models.UserSource{ UserID: string(anthroveUserID), } result := db.WithContext(ctx).Model(&userSource).Update("account_validate", valid) if result.Error != nil { return result.Error } return nil }