Skip to content

Commit 9e002b0

Browse files
authored
Implement route for exporting cache & auth for access to cache managing routes (#49)
1 parent 03f4bc4 commit 9e002b0

7 files changed

Lines changed: 110 additions & 44 deletions

File tree

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Scraper for Black Desert Online community data with a built-in API server. It currently supports EU, NA, SA and KR regions. Support for other regions might be added in the future.
33

44
## Projects using this API
5-
- ~~BDO Leaderboards ([Website](https://bdo.hemlo.cc/leaderboards), [sources](https://github.com/man90es/BDO-Leaderboards)): web-based leaderboards for Black Desert Online.~~
5+
- ~~BDO Leaderboards ([Sources](https://github.com/man90es/BDO-Leaderboards)): web-based leaderboards for Black Desert Online.~~
66
- Ikusa ([Website](https://ikusa.site), [sources](https://github.com/sch-28/ikusa_api)): powerful tool that allows you to analyze your game logs and gain valuable insights into your combat performance.
77
- GuildYapper ([Discord server](https://discord.gg/x2nKYuu2Z2)): Discord bot with various features for BDO guilds such as guild and player history logging, and automatic trial Discord management (more features TBA).
88
- BDO Guild Bosses - Alliance [EU] ([Discord server](https://discord.gg/735bYrQWKr)): Discord bot for organising events in a guild bosses alliance.
@@ -21,6 +21,10 @@ API documentation can be viewed [here](https://man90es.github.io/BDO-REST-API/).
2121
If you host the API yourself, either via Docker or natively, you can control some of its features by executing it with flags.
2222

2323
Available flags:
24+
- `-admintoken`
25+
- Specifies an admin token that gives access to certain endpoints
26+
- Type: string
27+
- Default value: none
2428
- `-cachettl`
2529
- Specifies cache TTL in minutes
2630
- Type: unsigned integer

cache/cache.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import (
1414
)
1515

1616
type CacheEntry[T any] struct {
17-
Data T
18-
Date time.Time
19-
Status int
17+
Data T `json:"data"`
18+
Date time.Time `json:"date"`
19+
Status int `json:"status"`
2020
}
2121

2222
type cache[T any] struct {
@@ -72,6 +72,17 @@ func (c *cache[T]) GetKeys() []string {
7272
return maps.Keys(c.internalCache.Items())
7373
}
7474

75+
func (c *cache[T]) GetValues() []CacheEntry[T] {
76+
items := c.internalCache.Items()
77+
result := make([]CacheEntry[T], 0, len(items))
78+
79+
for _, item := range items {
80+
result = append(result, item.Object.(CacheEntry[T]))
81+
}
82+
83+
return result
84+
}
85+
7586
var GuildProfiles = newCache[models.GuildProfile]()
7687
var GuildSearch = newCache[[]models.GuildProfile]()
7788
var Profiles = newCache[models.Profile]()

handlers/ListenAndServe.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ func ListenAndServe() {
1717
mux.HandleFunc("GET /v1", getStatus)
1818
mux.HandleFunc("GET /v1/adventurer", getAdventurer)
1919
mux.HandleFunc("GET /v1/adventurer/search", getAdventurerSearch)
20-
mux.HandleFunc("GET /v1/cache", getCache)
20+
mux.HandleFunc("GET /v1/cache", getCacheSummary)
21+
mux.HandleFunc("GET /v1/cache/{cacheType}", getCache)
2122
mux.HandleFunc("GET /v1/guild", getGuild)
2223
mux.HandleFunc("GET /v1/guild/search", getGuildSearch)
2324
mux.HandleFunc("/", catchall)

handlers/getCache.go

Lines changed: 24 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,35 @@ import (
44
"bdo-rest-api/cache"
55
"encoding/json"
66
"net/http"
7-
"strings"
87

9-
sf "github.com/sa-/slicefunk"
8+
"github.com/spf13/viper"
109
)
1110

12-
func getParseCacheKey(cacheType string) func(string) map[string]interface{} {
13-
return func(key string) map[string]interface{} {
14-
parts := strings.Split(key, ",")
15-
16-
switch cacheType {
17-
case "/adventurer":
18-
return map[string]interface{}{
19-
"region": parts[0],
20-
"profileTarget": parts[1],
21-
}
22-
case "/adventurer/search":
11+
func getCache(w http.ResponseWriter, r *http.Request) {
12+
if token := viper.GetString("admintoken"); len(token) > 0 {
13+
providedToken := r.Header.Get("Authorization")
2314

24-
return map[string]interface{}{
25-
"region": parts[0],
26-
"query": parts[1],
27-
"searhType": parts[2],
28-
}
29-
case "/guild":
30-
return map[string]interface{}{
31-
"region": parts[0],
32-
"guildName": parts[1],
33-
}
34-
case "/guild/search":
35-
return map[string]interface{}{
36-
"region": parts[0],
37-
"query": parts[1],
38-
}
39-
default:
40-
return nil
15+
if providedToken != "Bearer "+token {
16+
w.WriteHeader(http.StatusUnauthorized)
17+
return
4118
}
4219
}
43-
}
4420

45-
func getCache(w http.ResponseWriter, r *http.Request) {
46-
json.NewEncoder(w).Encode(map[string]interface{}{
47-
"/adventurer": sf.Map(cache.Profiles.GetKeys(), getParseCacheKey("/adventurer")),
48-
"/adventurer/search": sf.Map(cache.ProfileSearch.GetKeys(), getParseCacheKey("/adventurer/search")),
49-
"/guild": sf.Map(cache.GuildProfiles.GetKeys(), getParseCacheKey("/guild")),
50-
"/guild/search": sf.Map(cache.GuildSearch.GetKeys(), getParseCacheKey("/guild/search")),
51-
})
21+
cacheType := r.PathValue("cacheType")
22+
23+
if len(cacheType) == 0 {
24+
w.WriteHeader(http.StatusBadRequest)
25+
return
26+
}
27+
28+
switch cacheType {
29+
case "adventurer":
30+
w.WriteHeader(http.StatusOK)
31+
json.NewEncoder(w).Encode(cache.Profiles.GetValues())
32+
case "guild":
33+
w.WriteHeader(http.StatusOK)
34+
json.NewEncoder(w).Encode(cache.GuildProfiles.GetValues())
35+
default:
36+
w.WriteHeader(http.StatusBadRequest)
37+
}
5238
}

handlers/getCacheSummary.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package handlers
2+
3+
import (
4+
"bdo-rest-api/cache"
5+
"encoding/json"
6+
"net/http"
7+
"strings"
8+
9+
sf "github.com/sa-/slicefunk"
10+
"github.com/spf13/viper"
11+
)
12+
13+
func getParseCacheKey(cacheType string) func(string) map[string]interface{} {
14+
return func(key string) map[string]interface{} {
15+
parts := strings.Split(key, ",")
16+
17+
switch cacheType {
18+
case "/adventurer":
19+
return map[string]interface{}{
20+
"region": parts[0],
21+
"profileTarget": parts[1],
22+
}
23+
case "/adventurer/search":
24+
25+
return map[string]interface{}{
26+
"region": parts[0],
27+
"query": parts[1],
28+
"searhType": parts[2],
29+
}
30+
case "/guild":
31+
return map[string]interface{}{
32+
"region": parts[0],
33+
"guildName": parts[1],
34+
}
35+
case "/guild/search":
36+
return map[string]interface{}{
37+
"region": parts[0],
38+
"query": parts[1],
39+
}
40+
default:
41+
return nil
42+
}
43+
}
44+
}
45+
46+
func getCacheSummary(w http.ResponseWriter, r *http.Request) {
47+
if token := viper.GetString("admintoken"); len(token) > 0 {
48+
providedToken := r.Header.Get("Authorization")
49+
50+
if providedToken != "Bearer "+token {
51+
w.WriteHeader(http.StatusUnauthorized)
52+
return
53+
}
54+
}
55+
56+
json.NewEncoder(w).Encode(map[string]interface{}{
57+
"/adventurer": sf.Map(cache.Profiles.GetKeys(), getParseCacheKey("/adventurer")),
58+
"/adventurer/search": sf.Map(cache.ProfileSearch.GetKeys(), getParseCacheKey("/adventurer/search")),
59+
"/guild": sf.Map(cache.GuildProfiles.GetKeys(), getParseCacheKey("/guild")),
60+
"/guild/search": sf.Map(cache.GuildSearch.GetKeys(), getParseCacheKey("/guild/search")),
61+
})
62+
}

handlers/getStatus.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
)
1313

1414
var initTime = time.Now()
15-
var version = "1.12.5"
15+
var version = "1.13.0"
1616

1717
func getStatus(w http.ResponseWriter, r *http.Request) {
1818
json.NewEncoder(w).Encode(map[string]interface{}{

main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
)
1616

1717
func main() {
18+
flagAdminToken := flag.String("admintoken", "", "Admin token to allow certain endpoints")
1819
flagCacheTTL := flag.Uint("cachettl", 180, "Cache TTL in minutes")
1920
flagMaintenanceTTL := flag.Uint("maintenancettl", 5, "Allows to limit how frequently scraper can check for maintenance end in minutes")
2021
flagMongo := flag.String("mongo", "", "MongoDB connection string for loggig")
@@ -46,6 +47,7 @@ func main() {
4647
viper.Set("proxy", strings.Fields(os.Getenv("PROXY")))
4748
}
4849

50+
viper.Set("admintoken", *flagAdminToken)
4951
viper.Set("cachettl", time.Duration(*flagCacheTTL)*time.Minute)
5052
viper.Set("maintenancettl", time.Duration(*flagMaintenanceTTL)*time.Minute)
5153
viper.Set("maxtasksperclient", int(*flagMaxTasksPerClient))

0 commit comments

Comments
 (0)