summaryrefslogtreecommitdiff
path: root/backend-go/internal/api/setup.go
diff options
context:
space:
mode:
Diffstat (limited to 'backend-go/internal/api/setup.go')
-rw-r--r--backend-go/internal/api/setup.go231
1 files changed, 231 insertions, 0 deletions
diff --git a/backend-go/internal/api/setup.go b/backend-go/internal/api/setup.go
new file mode 100644
index 0000000..738deea
--- /dev/null
+++ b/backend-go/internal/api/setup.go
@@ -0,0 +1,231 @@
+package api
+
+import (
+"bytes"
+"compress/bzip2"
+"encoding/base64"
+"encoding/json"
+"fmt"
+"io"
+"net/http"
+"net/url"
+"os"
+"path/filepath"
+"strings"
+"time"
+)
+
+const (
+baseURL = "https://www.fuzzwork.co.uk/dump/latest"
+tokenURL = "https://login.eveonline.com/v2/oauth/token"
+authURL = "https://login.eveonline.com/v2/oauth/authorize"
+redirectURI = "https://localhost/callback/"
+dataDirPath = "../data"
+authFilePath = "./cache/auth.json"
+envFilePath = "../../.env"
+)
+
+var scopes = []string{
+"esi-assets.read_assets.v1",
+"esi-location.read_location.v1",
+"esi-location.read_online.v1",
+"esi-wallet.read_character_wallet.v1",
+"esi-ui.open_window.v1",
+"esi-ui.write_waypoint.v1",
+}
+
+var requiredFiles = map[string]string{
+"mapDenormalize.csv": fmt.Sprintf("%s/mapDenormalize.csv.bz2", baseURL),
+"mapJumps.csv": fmt.Sprintf("%s/mapJumps.csv.bz2", baseURL),
+"invTypes.csv": fmt.Sprintf("%s/invTypes.csv.bz2", baseURL),
+"mapRegions.csv": fmt.Sprintf("%s/mapRegions.csv.bz2", baseURL),
+}
+
+func StatusHandler(w http.ResponseWriter, r *http.Request) {
+ // Check if data is ready
+ dataReady := true
+ for fileName := range requiredFiles {
+ destPath := filepath.Join(dataDirPath, fileName)
+ if _, err := os.Stat(destPath); err != nil {
+ dataReady = false
+ break
+ }
+ }
+
+ // Check if auth is ready
+ authReady := false
+ if _, err := os.Stat(authFilePath); err == nil {
+ authReady = true
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]bool{
+ "data_ready": dataReady,
+ "auth_ready": authReady,
+ })
+}
+
+func DownloadDataHandler(w http.ResponseWriter, r *http.Request) {
+if err := os.MkdirAll(dataDirPath, os.ModePerm); err != nil {
+http.Error(w, fmt.Sprintf("Failed to create data dir: %v", err), http.StatusInternalServerError)
+return
+}
+
+for fileName, downloadURL := range requiredFiles {
+destPath := filepath.Join(dataDirPath, fileName)
+if _, err := os.Stat(destPath); err == nil {
+fmt.Printf("Skipping %s; already exists.\n", fileName)
+continue
+}
+
+fmt.Printf("Downloading %s from %s\n", fileName, downloadURL)
+resp, err := http.Get(downloadURL)
+if err != nil || resp.StatusCode != 200 {
+http.Error(w, fmt.Sprintf("Failed to download %s", fileName), http.StatusInternalServerError)
+if resp != nil {
+resp.Body.Close()
+}
+return
+}
+
+out, err := os.Create(destPath)
+if err != nil {
+http.Error(w, "Failed to create destination file", http.StatusInternalServerError)
+resp.Body.Close()
+return
+}
+
+bzReader := bzip2.NewReader(resp.Body)
+if _, err := io.Copy(out, bzReader); err != nil {
+http.Error(w, "Decompression failed", http.StatusInternalServerError)
+out.Close()
+resp.Body.Close()
+return
+}
+
+out.Close()
+resp.Body.Close()
+fmt.Printf("Wrote %s\n", destPath)
+}
+
+w.Header().Set("Content-Type", "application/json")
+w.WriteHeader(http.StatusOK)
+w.Write([]byte(`{"message": "EVE static data is ready."}`))
+}
+
+func GetAuthURLHandler(w http.ResponseWriter, r *http.Request) {
+clientID := os.Getenv("CLIENT_ID")
+if clientID == "" {
+http.Error(w, "CLIENT_ID not set in environment.", http.StatusInternalServerError)
+return
+}
+
+queryParams := url.Values{}
+queryParams.Add("response_type", "code")
+queryParams.Add("redirect_uri", redirectURI)
+queryParams.Add("client_id", clientID)
+queryParams.Add("state", "haul-local")
+queryParams.Add("scope", strings.Join(scopes, " "))
+
+fullURL := fmt.Sprintf("%s?%s", authURL, queryParams.Encode())
+w.Header().Set("Content-Type", "application/json")
+json.NewEncoder(w).Encode(map[string]string{"url": fullURL})
+}
+
+func CompleteAuthHandler(w http.ResponseWriter, r *http.Request) {
+var request struct {
+Code string `json:"code"`
+}
+if err := json.NewDecoder(r.Body).Decode(&request); err != nil || request.Code == "" {
+http.Error(w, "Valid 'code' is required.", http.StatusBadRequest)
+return
+}
+
+clientID := os.Getenv("CLIENT_ID")
+secretKey := os.Getenv("SECRET_KEY")
+
+if clientID == "" || secretKey == "" {
+http.Error(w, "Missing CLIENT_ID or SECRET_KEY", http.StatusInternalServerError)
+return
+}
+
+formValues := url.Values{}
+formValues.Set("grant_type", "authorization_code")
+formValues.Set("code", request.Code)
+
+req, err := http.NewRequest("POST", tokenURL, strings.NewReader(formValues.Encode()))
+if err != nil {
+http.Error(w, "Failed to create request.", http.StatusInternalServerError)
+return
+}
+
+userPass := clientID + ":" + secretKey
+encodedAuth := base64.StdEncoding.EncodeToString([]byte(userPass))
+req.Header.Add("Authorization", fmt.Sprintf("Basic %s", encodedAuth))
+req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+req.Header.Add("Host", "login.eveonline.com")
+
+client := &http.Client{Timeout: 30 * time.Second}
+resp, err := client.Do(req)
+if err != nil || resp.StatusCode >= 400 {
+http.Error(w, "Failed to swap token", http.StatusBadGateway)
+return
+}
+defer resp.Body.Close()
+
+var authData map[string]interface{}
+if err := json.NewDecoder(resp.Body).Decode(&authData); err != nil {
+http.Error(w, "Failed to decode ESI response", http.StatusInternalServerError)
+return
+}
+
+expiresIn := authData["expires_in"].(float64)
+authData["expires_at"] = time.Now().UTC().Unix() + int64(expiresIn)
+
+os.MkdirAll(filepath.Dir(authFilePath), os.ModePerm)
+cacheData, _ := json.MarshalIndent(authData, "", " ")
+os.WriteFile(authFilePath, cacheData, 0644)
+
+tokenStr, _ := authData["access_token"].(string)
+charInfo := extractJWTSubject(tokenStr)
+appendEnvValue("character_id", charInfo)
+
+w.Header().Set("Content-Type", "application/json")
+w.WriteHeader(http.StatusOK)
+w.Write([]byte(`{"message": "Auth successful, cache built."}`))
+}
+
+func extractJWTSubject(token string) string {
+parts := strings.Split(token, ".")
+if len(parts) < 2 {
+return "unknown"
+}
+payload, err := base64.RawURLEncoding.DecodeString(parts[1])
+if err != nil {
+return "unknown"
+}
+
+var claims map[string]interface{}
+if err := json.Unmarshal(payload, &claims); err != nil {
+return "unknown"
+}
+
+if sub, ok := claims["sub"].(string); ok {
+subParts := strings.Split(sub, ":")
+return subParts[len(subParts)-1]
+}
+return "unknown"
+}
+
+func appendEnvValue(key, value string) {
+content, err := os.ReadFile(envFilePath)
+if err != nil {
+content = []byte{}
+}
+
+if !bytes.Contains(content, []byte(key+"=")) {
+f, _ := os.OpenFile(envFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+defer f.Close()
+f.WriteString(fmt.Sprintf("\n%s=%s\n", key, value))
+}
+}