package handlers import ( "net/http" "strconv" "xorm.io/xorm" "github.com/forgeo/forgebucket/internal/models" ) type ExploreHandler struct{ db *xorm.Engine } func NewExploreHandler(db *xorm.Engine) *ExploreHandler { return &ExploreHandler{db: db} } type exploreRepo struct { ID int64 `json:"id"` Name string `json:"name"` Description string `json:"description"` IsPrivate bool `json:"isPrivate"` DefaultBranch string `json:"defaultBranch"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` OwnerID int64 `json:"ownerId"` OwnerUsername string `json:"ownerUsername"` OwnerAvatarURL string `json:"ownerAvatarUrl"` } type exploreUser struct { ID int64 `json:"id"` Username string `json:"username"` AvatarURL string `json:"avatarUrl"` CreatedAt string `json:"createdAt"` } func (h *ExploreHandler) Repos(w http.ResponseWriter, r *http.Request) { q := r.URL.Query().Get("q") sort := r.URL.Query().Get("sort") limit := clampInt(r.URL.Query().Get("limit"), 20, 1, 50) offset := clampInt(r.URL.Query().Get("offset"), 0, 0, 1<<30) sess := h.db.Where("is_private = false") if q != "" { sess = sess.And("(name ILIKE ? OR description ILIKE ?)", "%"+q+"%", "%"+q+"%") } switch sort { case "name": sess = sess.Asc("name") case "created": sess = sess.Desc("created_at") default: sess = sess.Desc("updated_at") } var repos []models.Repository if err := sess.Limit(limit, offset).Find(&repos); err != nil { jsonError(w, "could not search repositories", http.StatusInternalServerError) return } results := make([]exploreRepo, 0, len(repos)) for _, repo := range repos { var owner models.User found, _ := h.db.ID(repo.OwnerID).Cols("id", "username", "avatar_url").Get(&owner) if !found { continue } results = append(results, exploreRepo{ ID: repo.ID, Name: repo.Name, Description: repo.Description, IsPrivate: repo.IsPrivate, DefaultBranch: repo.DefaultBranch, CreatedAt: repo.CreatedAt.Format("2006-01-02T15:04:05Z"), UpdatedAt: repo.UpdatedAt.Format("2006-01-02T15:04:05Z"), OwnerID: owner.ID, OwnerUsername: owner.Username, OwnerAvatarURL: owner.AvatarURL, }) } jsonOK(w, results) } func (h *ExploreHandler) Users(w http.ResponseWriter, r *http.Request) { q := r.URL.Query().Get("q") limit := clampInt(r.URL.Query().Get("limit"), 20, 1, 50) offset := clampInt(r.URL.Query().Get("offset"), 0, 0, 1<<30) sess := h.db.Cols("id", "username", "avatar_url", "created_at") if q != "" { sess = sess.Where("username ILIKE ?", "%"+q+"%") } else { sess = sess.Where("1 = 1") } sess = sess.Asc("username") var users []models.User if err := sess.Limit(limit, offset).Find(&users); err != nil { jsonError(w, "could not search users", http.StatusInternalServerError) return } results := make([]exploreUser, 0, len(users)) for _, u := range users { results = append(results, exploreUser{ ID: u.ID, Username: u.Username, AvatarURL: u.AvatarURL, CreatedAt: u.CreatedAt.Format("2006-01-02T15:04:05Z"), }) } jsonOK(w, results) } func clampInt(s string, def, min, max int) int { v, err := strconv.Atoi(s) if err != nil { return def } if v < min { return min } if v > max { return max } return v }