399 lines
12 KiB
Go
399 lines
12 KiB
Go
|
package postgres
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"errors"
|
||
|
"time"
|
||
|
|
||
|
otterError "git.dragse.it/anthrove/otter-space-sdk/v2/pkg/error"
|
||
|
"git.dragse.it/anthrove/otter-space-sdk/v2/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
|
||
|
}
|
||
|
|
||
|
// TODO: FIX THE TEST
|
||
|
func GetUserFavoriteWithPagination(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, skip int, limit int) (*models.FavoriteList, error) {
|
||
|
var favoritePosts []models.Post
|
||
|
|
||
|
if anthroveUserID == "" {
|
||
|
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
|
||
|
}
|
||
|
|
||
|
if len(anthroveUserID) != 25 {
|
||
|
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
|
||
|
}
|
||
|
|
||
|
db.WithContext(ctx).Joins("RIGHT JOIN \"UserFavorites\" AS of ON \"Post\".id = of.post_id AND of.user_id = ?", anthroveUserID).Preload("References").Offset(skip).Limit(limit).Find(&favoritePosts)
|
||
|
|
||
|
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
|
||
|
}
|