feat(backend): leaderboard tiers, N+1 fix, contributors, certificate approval, physical disable
- leaderboard: batch-fetch users (GetUsersByIDs), deterministic tie-break by user_id - leaderboard: tier system (champion/gold/silver/bronze) based on rank percentile - store: HasCompletedCurriculum requires cert.Status == "issued"; pending does not satisfy - certificate: issueCertificateIfEligible creates pending row; admin approves via handleAdminIssueCertificate - certificate: handleRequestPhysicalCertificate returns 410 Gone (feature disabled) - settings: contributors CRUD (loadContributors, validateContributors, handleAdminContributorAvatarUpload) - community: expose contributors in /api/community response - rate_limit: separate DevMode bypass from redis-nil check in allowWSHandshake - router: remove /api/search route; fix missing tab indent on /api/meta line - migration 0032: contributors app_setting with seed entry
This commit is contained in:
@@ -504,69 +504,6 @@ func (s *Server) handleMeActivity(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"days": days, "weekDays": week, "monthDays": month})
|
||||
}
|
||||
|
||||
func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) {
|
||||
q := strings.TrimSpace(r.URL.Query().Get("q"))
|
||||
if utf8.RuneCountInString(q) < 2 {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"results": []any{}})
|
||||
return
|
||||
}
|
||||
me := currentUser(r)
|
||||
like := "%" + q + "%"
|
||||
var results []map[string]any
|
||||
rows, err := s.store.Pool().Query(r.Context(), `
|
||||
SELECT id, title FROM lessons WHERE title ILIKE $1 ORDER BY id LIMIT 8`, like)
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
var title string
|
||||
if rows.Scan(&id, &title) == nil {
|
||||
results = append(results, map[string]any{"kind": "lesson", "title": title, "href": "/player/" + strconv.FormatInt(id, 10)})
|
||||
}
|
||||
}
|
||||
rows.Close()
|
||||
}
|
||||
rows, err = s.store.Pool().Query(r.Context(), `
|
||||
SELECT id, title FROM events WHERE title ILIKE $1 ORDER BY starts_at DESC LIMIT 8`, like)
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
var title string
|
||||
if rows.Scan(&id, &title) == nil {
|
||||
results = append(results, map[string]any{"kind": "event", "title": title, "href": "/unwrap"})
|
||||
}
|
||||
}
|
||||
rows.Close()
|
||||
}
|
||||
if me.Role.IsStaff() {
|
||||
people, _, _ := s.store.ListUsers(r.Context(), q, "", 8, 0)
|
||||
for _, p := range people {
|
||||
results = append(results, map[string]any{"kind": "person", "title": p.Name, "href": "/profile/" + strconv.FormatInt(p.ID, 10)})
|
||||
}
|
||||
} else {
|
||||
people, _, _ := s.store.ListActiveByRoles(r.Context(), []string{"mentor", "admin"}, 40, 0)
|
||||
n := 0
|
||||
for _, p := range people {
|
||||
if n >= 8 || !strings.Contains(strings.ToLower(p.Name), strings.ToLower(q)) {
|
||||
continue
|
||||
}
|
||||
results = append(results, map[string]any{"kind": "person", "title": p.Name, "href": "/profile/" + strconv.FormatInt(p.ID, 10)})
|
||||
n++
|
||||
}
|
||||
}
|
||||
pages := []struct{ title, href string }{
|
||||
{"درخت مهارت", "/cap"}, {"اعلانها", "/notifications"}, {"لیگ", "/leaderboard"},
|
||||
{"تنظیمات", "/settings"}, {"پروفایل", "/profile"},
|
||||
}
|
||||
for _, p := range pages {
|
||||
if strings.Contains(p.title, q) {
|
||||
results = append(results, map[string]any{"kind": "page", "title": p.title, "href": p.href})
|
||||
}
|
||||
}
|
||||
if results == nil {
|
||||
results = []map[string]any{}
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"results": results})
|
||||
}
|
||||
|
||||
func (s *Server) handleMeta(w http.ResponseWriter, r *http.Request) {
|
||||
env := "production"
|
||||
|
||||
Reference in New Issue
Block a user