Skip to content

Commit 6a4af90

Browse files
committed
Addressing comments
1 parent eae4452 commit 6a4af90

18 files changed

Lines changed: 573 additions & 471 deletions

File tree

api/api.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import (
66
"github.com/go-chi/chi/v5"
77
)
88

9-
func Router(steamWebAPIKey string) chi.Router {
9+
func Router() chi.Router {
1010
r := chi.NewRouter()
11-
r.Mount("/v1", v1.Router(steamWebAPIKey))
11+
r.Mount("/v1", v1.Router())
1212
return r
1313
}

api/v1/steam/resolve.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package steam
2+
3+
import (
4+
"net/http"
5+
"strings"
6+
7+
"reverse-watch/domain/models"
8+
"reverse-watch/errors"
9+
"reverse-watch/middleware"
10+
"reverse-watch/render"
11+
steamservice "reverse-watch/service/steam"
12+
)
13+
14+
type resolveSteamIDResponse struct {
15+
SteamID models.SteamID `json:"steam_id"`
16+
}
17+
18+
func resolveSteamID(w http.ResponseWriter, r *http.Request) {
19+
steamSvc, ok := r.Context().Value(middleware.SteamServiceContextKey).(*steamservice.Service)
20+
if !ok || steamSvc == nil {
21+
render.Errorf(w, r, errors.InternalServerError, "steam service not configured")
22+
return
23+
}
24+
25+
raw := strings.TrimSpace(r.URL.Query().Get("vanityUrl"))
26+
if raw == "" {
27+
render.Errorf(w, r, errors.BadRequest, "missing vanityUrl")
28+
return
29+
}
30+
31+
id, err := steamSvc.ResolveSteamID(r.Context(), raw)
32+
if err != nil {
33+
render.Error(w, r, err)
34+
return
35+
}
36+
37+
render.JSON(w, r, &resolveSteamIDResponse{SteamID: *id})
38+
}

api/v1/steam/router.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package steam
2+
3+
import (
4+
"time"
5+
6+
"reverse-watch/ratelimit"
7+
8+
"github.com/go-chi/chi/v5"
9+
)
10+
11+
func Router() chi.Router {
12+
r := chi.NewRouter()
13+
r.With(ratelimit.ThrottleByIP(time.Minute, 100)).Get("/resolve-vanity", resolveSteamID)
14+
return r
15+
}

api/v1/users/router.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import (
88
"github.com/go-chi/chi/v5"
99
)
1010

11-
func Router(steamWebAPIKey string) chi.Router {
11+
func Router() chi.Router {
1212
r := chi.NewRouter()
13-
r.With(ratelimit.ThrottleByIP(time.Minute, 100)).Get("/{steamId}", fetchUserStatus(steamWebAPIKey))
13+
r.With(ratelimit.ThrottleByIP(time.Minute, 100)).Get("/{steamId}", fetchUserStatus)
1414
return r
1515
}

api/v1/users/users.go

Lines changed: 29 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package users
22

33
import (
4-
"context"
54
"net/http"
6-
"strings"
7-
"time"
85

96
"reverse-watch/domain/dto"
107
"reverse-watch/domain/models"
@@ -22,54 +19,39 @@ type fetchUserStatusResponse struct {
2219
LastReversalTimestamp *uint64 `json:"last_reversal_timestamp,omitempty"`
2320
}
2421

25-
func fetchUserStatus(steamWebAPIKey string) http.HandlerFunc {
26-
return func(w http.ResponseWriter, r *http.Request) {
27-
factory := r.Context().Value(middleware.FactoryContextKey).(repository.Factory)
22+
func fetchUserStatus(w http.ResponseWriter, r *http.Request) {
23+
factory := r.Context().Value(middleware.FactoryContextKey).(repository.Factory)
2824

29-
steamIdStr := chi.URLParam(r, "steamId")
30-
client := &http.Client{Timeout: 15 * time.Second}
31-
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
32-
defer cancel()
33-
34-
var steamId *models.SteamID
35-
var err error
36-
if strings.TrimSpace(steamWebAPIKey) != "" {
37-
steamId, err = models.ParseSteamUserInputWithOpts(ctx, client, steamIdStr, &models.SteamUserInputOpts{
38-
UseWebAPIForVanity: true,
39-
SteamWebAPIKey: steamWebAPIKey,
40-
})
41-
} else {
42-
steamId, err = models.ParseSteamUserInput(ctx, client, steamIdStr)
43-
}
44-
if err != nil {
45-
render.Errorf(w, r, errors.BadRequest, "invalid steam id")
46-
return
47-
}
25+
steamIdStr := chi.URLParam(r, "steamId")
26+
steamId, err := models.ToSteamID(steamIdStr)
27+
if err != nil {
28+
render.Errorf(w, r, errors.BadRequest, "invalid steam id")
29+
return
30+
}
4831

49-
reversals, err := factory.Reversal().List(&dto.ReversalListOptions{
50-
SteamID: steamId,
51-
OrderParam: &dto.OrderParam{Column: "id", Direction: dto.DESC},
52-
})
53-
if err != nil {
54-
render.Errorf(w, r, errors.InternalServerError, "failed to list reversals for steam id %q", steamId)
55-
return
56-
}
32+
reversals, err := factory.Reversal().List(&dto.ReversalListOptions{
33+
SteamID: steamId,
34+
OrderParam: &dto.OrderParam{Column: "id", Direction: dto.DESC},
35+
})
36+
if err != nil {
37+
render.Errorf(w, r, errors.InternalServerError, "failed to list reversals for steam id %q", steamId)
38+
return
39+
}
5740

58-
data := &fetchUserStatusResponse{
59-
SteamID: *steamId,
60-
}
41+
data := &fetchUserStatusResponse{
42+
SteamID: *steamId,
43+
}
6144

62-
var lastReversalTime uint64
63-
for _, reversal := range reversals {
64-
lastReversalTime = max(lastReversalTime, reversal.ReversedAt)
65-
if reversal.ExpungedAt == nil {
66-
data.HasReversed = true
67-
break
68-
}
45+
var lastReversalTime uint64
46+
for _, reversal := range reversals {
47+
lastReversalTime = max(lastReversalTime, reversal.ReversedAt)
48+
if reversal.ExpungedAt == nil {
49+
data.HasReversed = true
50+
break
6951
}
70-
if data.HasReversed {
71-
data.LastReversalTimestamp = &lastReversalTime
72-
}
73-
render.JSON(w, r, data)
7452
}
53+
if data.HasReversed {
54+
data.LastReversalTimestamp = &lastReversalTime
55+
}
56+
render.JSON(w, r, data)
7557
}

api/v1/users/users_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ func TestFetchUserStatus(t *testing.T) {
444444
}
445445

446446
factoryMiddleware := middleware.FactoryMiddleware(f)
447-
handler := fetchUserStatus("")
447+
handler := http.HandlerFunc(fetchUserStatus)
448448

449449
finalHandler := factoryMiddleware(handler)
450450

api/v1/v1.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@ import (
55
"reverse-watch/api/v1/health"
66
"reverse-watch/api/v1/marketplace"
77
"reverse-watch/api/v1/reversals"
8+
"reverse-watch/api/v1/steam"
89
"reverse-watch/api/v1/users"
910

1011
"github.com/go-chi/chi/v5"
1112
)
1213

13-
func Router(steamWebAPIKey string) chi.Router {
14+
func Router() chi.Router {
1415
r := chi.NewRouter()
1516
r.Mount("/health", health.Router())
1617
r.Mount("/marketplace", marketplace.Router())
1718
r.Mount("/reversals", reversals.Router())
18-
r.Mount("/users", users.Router(steamWebAPIKey))
19+
r.Mount("/users", users.Router())
20+
r.Mount("/steam", steam.Router())
1921
r.Mount("/admin", admin.Router())
2022
return r
2123
}

config.example.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@
1313
},
1414
"Environment": "development",
1515
"Steam": {
16-
"WebAPIKey": ""
16+
"WebAPIKeys": ["api-key-1", "api-key-2"]
1717
}
1818
}

config/config.go

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99

1010
"reverse-watch/domain/models/constants"
11+
"reverse-watch/util"
1112

1213
"github.com/go-viper/mapstructure/v2"
1314
"github.com/spf13/viper"
@@ -41,10 +42,10 @@ type Config struct {
4142
}
4243
}
4344

44-
// Steam.WebAPIKey is optional. If set, /api/v1/users resolves /id/{vanity} via the Web API
45-
// instead of the default ?xml=1 request to the community site.
45+
// Steam.WebAPIKeys is optional. If set, /api/v1/steam/resolve-vanity can use the Steam Web API
46+
// (keys are rotated) instead of any fallback resolution.
4647
Steam struct {
47-
WebAPIKey string
48+
WebAPIKeys *util.Ring[string]
4849
}
4950
}
5051

@@ -69,6 +70,34 @@ func load() Config {
6970
return nil, fmt.Errorf("invalid environment")
7071
}
7172
},
73+
func(from reflect.Value, to reflect.Value) (interface{}, error) {
74+
ringType := reflect.TypeOf(&util.Ring[string]{})
75+
if to.Type() != ringType {
76+
return from.Interface(), nil
77+
}
78+
79+
keys := make([]string, 0)
80+
switch from.Kind() {
81+
case reflect.String:
82+
for _, part := range strings.Split(from.String(), ",") {
83+
part = strings.TrimSpace(part)
84+
if part != "" {
85+
keys = append(keys, part)
86+
}
87+
}
88+
case reflect.Slice, reflect.Array:
89+
for i := 0; i < from.Len(); i++ {
90+
val := strings.TrimSpace(fmt.Sprint(from.Index(i).Interface()))
91+
if val != "" {
92+
keys = append(keys, val)
93+
}
94+
}
95+
default:
96+
return util.NewRing[string](nil), nil
97+
}
98+
99+
return util.NewRing(keys), nil
100+
},
72101
))
73102

74103
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
@@ -91,7 +120,7 @@ func load() Config {
91120
// Need to register environment variables if defaults aren't set
92121
v.BindEnv("HTTP.AllowedOrigins")
93122
v.BindEnv("Ingestors.CSFloat.SecretKey")
94-
v.BindEnv("Steam.WebAPIKey")
123+
v.BindEnv("Steam.WebAPIKeys")
95124

96125
// Try to find the root directory, but don't panic if it fails since go.mod doesn't exist in production
97126
dir, err := GetProjectRootDir()

0 commit comments

Comments
 (0)