package classes import ( "context" "crypto/md5" "errors" "fmt" "strconv" "strings" "git.golem15.com/golem15/sm-user-plugin/models" "git.golem15.com/golem15/summercms/modules/backpack" "git.golem15.com/golem15/summercms/modules/lagoon/attach" "gocloud.dev/blob" "gorm.io/gorm" ) // AvatarURL returns the display picture URL of user at size pixels, the // rule the user API payload follows: the thumbnail (size by size, mode // auto) of the user's avatar attachment when there is one and a file // bucket is published, the attachment's file path when the thumbnail // fails (logged), and otherwise the gravatar URL // //www.gravatar.com/avatar/{md5 of the email}?s={size}&d=mm. The URL may // be protocol-relative or relative; a caller that needs an absolute URL // resolves it. Only a failing database lookup is an error. func AvatarURL(ctx context.Context, app *backpack.App, user *models.User, size int) (string, error) { if user == nil { return "", errors.New("golem15.user: avatar needs a user") } fallback := gravatar(user.Email, size) if app == nil || user.ID == 0 { return fallback, nil } gdb, ok := app.Lookup[*gorm.DB]() if !ok || gdb == nil { return fallback, nil } var f attach.File err := gdb.WithContext(ctx). Where("attachment_type = ? AND attachment_id = ? AND field = ?", user.MorphName(), strconv.FormatUint(uint64(user.ID), 10), models.AvatarField). Take(&f).Error if errors.Is(err, gorm.ErrRecordNotFound) { return fallback, nil } if err != nil { return "", err } bucket, ok := app.Lookup[*blob.Bucket]() if !ok || bucket == nil { return fallback, nil } url, err := f.Thumb(ctx, bucket, size, size, "auto") if err != nil { pluginLog.Error("golem15.user: avatar thumb", "err", err) return "/" + attach.BlobKey(f.DiskName), nil } return url, nil } // gravatar is the gravatar URL of an email at size pixels. func gravatar(email string, size int) string { sum := md5.Sum([]byte(strings.ToLower(strings.TrimSpace(email)))) return fmt.Sprintf("//www.gravatar.com/avatar/%x?s=%d&d=mm", sum, size) }