Files
zeekayandHanzo Dev aa87442fdd Record upstream authorship centrally in NOTICE instead of in every file header
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the
Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in
NOTICE, alongside LICENSE. The header lint accepts any copyright line above the
SPDX line, so it no longer demands an upstream one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 17:50:35 -07:00

92 lines
2.8 KiB
Go

// Copyright 2026 Hanzo AI, Inc. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"context"
"fmt"
"image/png"
"io"
"net/url"
"strconv"
"github.com/hanzoai/git/models/db"
"github.com/hanzoai/git/modules/avatar"
"github.com/hanzoai/git/modules/httplib"
"github.com/hanzoai/git/modules/log"
"github.com/hanzoai/git/modules/setting"
"github.com/hanzoai/git/modules/storage"
)
// CustomAvatarRelativePath returns repository custom avatar file path.
func (repo *Repository) CustomAvatarRelativePath() string {
return repo.Avatar
}
// ExistsWithAvatarAtStoragePath returns true if there is a user with this Avatar
func ExistsWithAvatarAtStoragePath(ctx context.Context, storagePath string) (bool, error) {
// See func (repo *Repository) CustomAvatarRelativePath()
// repo.Avatar is used directly as the storage path - therefore we can check for existence directly using the path
return db.GetEngine(ctx).Where("`avatar`=?", storagePath).Exist(new(Repository))
}
// RelAvatarLink returns a relative link to the repository's avatar.
func (repo *Repository) RelAvatarLink(ctx context.Context) string {
return repo.relAvatarLink(ctx)
}
// generateRandomAvatar generates a random avatar for repository.
func generateRandomAvatar(ctx context.Context, repo *Repository) error {
idToString := strconv.FormatInt(repo.ID, 10)
seed := idToString
img := avatar.RandomImageDefaultSize([]byte(seed))
repo.Avatar = idToString
if err := storage.SaveFrom(storage.RepoAvatars, repo.CustomAvatarRelativePath(), func(w io.Writer) error {
return png.Encode(w, img)
}); err != nil {
return fmt.Errorf("failed to create dir %s: %w", repo.CustomAvatarRelativePath(), err)
}
log.Info("New random avatar created for repository: %d", repo.ID)
if _, err := db.GetEngine(ctx).ID(repo.ID).Cols("avatar").NoAutoTime().Update(repo); err != nil {
return err
}
return nil
}
func (repo *Repository) relAvatarLink(ctx context.Context) string {
// If no avatar - path is empty
avatarPath := repo.CustomAvatarRelativePath()
if len(avatarPath) == 0 {
switch mode := setting.RepoAvatar.Fallback; mode {
case "image":
return setting.RepoAvatar.FallbackImage
case "random":
if err := generateRandomAvatar(ctx, repo); err != nil {
log.Error("generateRandomAvatar: %v", err)
}
default:
// default behaviour: do not display avatar
return ""
}
}
return setting.AppSubURL + "/repo-avatars/" + url.PathEscape(repo.Avatar)
}
// AvatarLink returns the full avatar url with http host or the empty string if the repo doesn't have an avatar.
//
// TODO: refactor it to a relative URL, but it is still used in API response at the moment
func (repo *Repository) AvatarLink(ctx context.Context) string {
relLink := repo.relAvatarLink(ctx)
if relLink != "" {
return httplib.MakeAbsoluteURL(ctx, relLink)
}
return ""
}