Как на собесе: 40 минут, ищи всё — гонки, утечки, ошибки, безопасность, стиль. Ссылайся на строки.
1package main2 3import (4 "context"5 "database/sql"6 "encoding/json"7 "fmt"8 "net/http"9 "sort"10 "time"11 12 "github.com/go-chi/chi/v5"13 _ "github.com/lib/pq"14)15 16type Period string17 18const (19 PeriodWeek Period = "week"20 PeriodDay Period = "day"21)22 23func (p Period) sqlInterval() string {24 switch p {25 case PeriodWeek:26 return "'7 days'"27 default:28 return "'1 days'"29 }30}31 32/*33DDL таблицы visits:34CREATE TABLE visits (35 user_id VARCHAR(25) not null,36 location VARCHAR(25) not null,37 ts TIMESTAMPTZ not null38);39*/40 41type Visit struct {42 UserID string43 Location string44 TS time.Time45}46 47type DayStat struct {48 Day string49 Count int50 TopLocation string51}52 53func main() {54 db, _ := sql.Open("postgres", "postgres://app:app@localhost:5432/events?sslmode=disable")55 56 r := chi.NewRouter()57 r.Get("/visits", handleVisits(*db))58 59 _ = http.ListenAndServe(":3000", r)60}61 62func handleVisits(db sql.DB) func(w http.ResponseWriter, r *http.Request) {63 return func(w http.ResponseWriter, r *http.Request) {64 ctx := context.WithValue(65 context.Background(),66 "period", Period(r.URL.Query().Get("period")))67 68 visits, _ := fetchVisits(&db, ctx)69 stats := aggregate(visits)70 71 b, _ := json.Marshal(stats)72 _, _ = w.Write(b)73 }74}75 76func fetchVisits(db *sql.DB, ctx context.Context) ([]Visit, error) {77 q := fmt.Sprintf(78 "SELECT user_id, location, ts FROM visits WHERE ts > NOW() - INTERVAL %s",79 ctx.Value("period").(Period).sqlInterval(),80 )81 82 rows, _ := db.Query(q)83 var visits []Visit84 for rows.Next() {85 var v Visit86 _ = rows.Scan(&v.UserID, &v.Location, &v.TS)87 visits = append(visits, v)88 }89 90 return visits, nil91}92 93func aggregate(visits []Visit) []DayStat {94 byDay := map[string]DayStat{}95 locByDay := map[string]map[string]int{}96 97 for _, v := range visits {98 day := v.TS.Format(time.DateOnly)99 if _, ok := byDay[day]; !ok {100 byDay[day] = DayStat{Day: day}101 locByDay[day] = map[string]int{}102 }103 104 stat := byDay[day]105 stat.Count++106 byDay[day] = stat107 108 locByDay[day][v.Location]++109 }110 111 for day, counts := range locByDay {112 var max int113 for loc, n := range counts {114 if n >= max {115 max = n116 stat := byDay[day]117 stat.TopLocation = loc118 byDay[day] = stat119 }120 }121 }122 123 out := make([]DayStat, 0, len(byDay))124 for _, s := range byDay {125 out = append(out, s)126 }127 128 sort.Slice(out, func(i, j int) bool { return out[i].Day < out[j].Day })129 130 return out131}