// NGnius 2020-01-30 package main import ( "encoding/json" "fmt" "io/ioutil" "net/http" "net/url" "strconv" "strings" ) const ( defaultCorsHeader = "*" defaultPassword = "" ) var ( corsHeader string password string reuseTokens bool ) func boardHandler(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") w.Header().Add("Access-Control-Allow-Origin", corsHeader) if r.Method != "GET" { //w.WriteHeader(405) errorResponse(405, "Non-GET method not allowed at this endpoint", w, r) return } args := r.URL.Query() // check args pre-conditions if !(checkArgExists(args, "board", w) || checkArgExists(args, "name", w) || (checkArgExists(args, "id", w) && checkArgInt(args, "id", w, 0))) { errorResponse(400, "Missing required 'board' URL parameter", w, r) return } board := "" var boardId int = -1 if checkArgExists(args, "name", w) { board = args.Get("name") } if checkArgExists(args, "board", w) { board = args.Get("board") } if checkArgExists(args, "id", w) { boardId, _ = strconv.Atoi(args.Get("id")) } if !checkArgExists(args, "count", w) || !checkArgInt(args, "count", w, 0) { //w.WriteHeader(400) errorResponse(400, "Missing required 'count' integer URL parameter", w, r) return } count, _ := strconv.Atoi(args.Get("count")) if !checkArgExists(args, "start", w) || !checkArgInt(args, "start", w, 0) { //w.WriteHeader(400) errorResponse(400, "Missing required 'start' integer URL parameter", w, r) return } start, _ := strconv.Atoi(args.Get("start")) // execute query result := NewResult("", r.URL.String()) var b *Board var err error if (boardId == -1) { b, err = boardByName(board) } else { b, err = boardById(int64(boardId)) } //b, ok := boards[board] if err != nil { fmt.Println(err) //w.WriteHeader(404) errorResponse(404, "Board could not be retrieved: "+err.Error(), w, r) return } bEntries, loadErr := b.SomeEntries(int64(start), int64(start+count)) if loadErr != nil { fmt.Println(loadErr) //w.WriteHeader(404) errorResponse(404, "Board entries could not be retrieved: "+loadErr.Error(), w, r) return } for _, entry := range bEntries { item, entryErr := entry.JsonObject() if entryErr != nil { fmt.Println(entryErr) } result.Items = append(result.Items, &item) } result.Query = fmt.Sprintf("load board[name: %s] from %d to %d", board, start, count+start) result.Complete() data, err := json.Marshal(result) if err != nil { //w.WriteHeader(500) errorResponse(500, "Unable to convert result into JSON: "+err.Error(), w, r) return } w.Write(data) } func playerHandler(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") w.Header().Add("Access-Control-Allow-Origin", corsHeader) if r.Method != "GET" { //w.WriteHeader(405) errorResponse(405, "Non-GET method not allowed at this endpoint", w, r) return } args := r.URL.Query() // check args if !checkArgExists(args, "id", w) || !checkArgInt(args, "id", w, 1) { errorResponse(400, "Missing required 'id' integer URL parameter", w, r) return } id, _ := strconv.Atoi(args.Get("id")) entry_count := 0 if checkArgExists(args, "entries", w) && checkArgInt(args, "entries", w, 0) { entry_count, _ = strconv.Atoi(args.Get("entries")) } // retrieve player result := NewResult("", r.URL.String()) player := &Player{ID: int64(id)} loadErr := player.Load() if loadErr != nil { fmt.Println(loadErr) //w.WriteHeader(404) errorResponse(404, "Player could not be retrieved: "+loadErr.Error(), w, r) return } tempJsonObj, _ := player.JsonObject() pJsonObj := tempJsonObj.(PlayerJSON) if entry_count > 0 { entries, loadErr := player.SomeEntries(int64(entry_count)) if loadErr == nil { for _, e := range entries { eJsonObj, entryErr := e.JsonObject() if entryErr != nil { fmt.Println(entryErr) } pJsonObj.Entries = append(pJsonObj.Entries, eJsonObj.(EntryJSON)) } } } result.Items = []interface{}{pJsonObj} result.Query = fmt.Sprintf("load player[id: %d]", player.ID) result.Complete() data, err := json.Marshal(result) if err != nil { //w.WriteHeader(500) errorResponse(500, "Unable convert result to JSON: "+err.Error(), w, r) return } w.Write(data) } func newEntryHandler(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") w.Header().Add("Access-Control-Allow-Origin", corsHeader) if r.Method != "POST" { //w.WriteHeader(405) errorResponse(405, "Non-POST method not allowed at this endpoint", w, r) return } // check for API token tokenHeader := r.Header.Get("Authorization") tokenSplit := strings.Split(tokenHeader, " ") if tokenHeader == "" || len(tokenSplit) != 2 || tokenSplit[1] == "" { errorResponse(401, "Missing or invalid authorization header", w, r) return } key, keyErr := keyByToken(tokenSplit[1]) if keyErr != nil || key.ID == 0 { errorResponse(401, "Invalid token", w, r) return } if !key.IsEnabled() { errorResponse(403, "Invalid token", w, r) return } data, readErr := ioutil.ReadAll(r.Body) if readErr != nil { fmt.Println(readErr) //w.WriteHeader(500) errorResponse(500, "Unable to read HTTP request body: "+readErr.Error(), w, r) return } newEntry, jsonErr := UnmarshalNewEntryJSON(data) if jsonErr != nil { //w.WriteHeader(400) errorResponse(400, "Unable to convert request to JSON: "+jsonErr.Error(), w, r) return } if password != "" && newEntry.Password != password { errorResponse(403, "Invalid password", w, r) return } if newEntry.PlayerID != key.Player && !key.IsMultiuser() { errorResponse(403, "Invalid authorization for player", w, r) return } sqlErr := newEntrySql(newEntry.Score, newEntry.PlayerID, newEntry.BoardID) if sqlErr != nil { fmt.Println(sqlErr) //w.WriteHeader(500) errorResponse(500, "Entry could not be created: "+sqlErr.Error(), w, r) return } //w.WriteHeader(204) errorResponse(200, "New entry created", w, r) } func newKeyHandler(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") w.Header().Add("Access-Control-Allow-Origin", corsHeader) if r.Method != "POST" { errorResponse(405, "Non-POST method not allowed at this endpoint", w, r) return } data, readErr := ioutil.ReadAll(r.Body) if readErr != nil { fmt.Println(readErr) errorResponse(500, "Unable to read HTTP request body: "+readErr.Error(), w, r) return } result := NewResult("", r.URL.String()) newKey, jsonErr := UnmarshalNewKeyJSON(data) if jsonErr != nil { errorResponse(400, "Unable to convert request to JSON: "+jsonErr.Error(), w, r) return } var key *Key var sqlErr error if reuseTokens { key, sqlErr = keyByPlayer(newKey.PlayerID) } if (sqlErr != nil && reuseTokens) || !reuseTokens { key, sqlErr = newKeySql(newKey.PlayerID, newKey.PlayerName) } if sqlErr != nil { fmt.Println(sqlErr) errorResponse(500, "Key could not be created: "+sqlErr.Error(), w, r) return } jsonObj, _ := key.JsonObject() result.Items = []interface{}{jsonObj} result.Query = fmt.Sprintf("new key[player id: %d]", newKey.PlayerID) result.Complete() data, err := json.Marshal(result) if err != nil { errorResponse(500, "Unable convert result to JSON: "+err.Error(), w, r) return } w.Write(data) } func exampleHandler(w http.ResponseWriter, r *http.Request) { // useless function please ignore } // utility functions func checkArgExists(values url.Values, key string, w http.ResponseWriter) (ok bool) { ok = values.Get(key) != "" return } func checkArgInt(values url.Values, key string, w http.ResponseWriter, min int) (ok bool) { intVal, err := strconv.Atoi(values.Get(key)) ok = err == nil && intVal >= min return } func errorResponse(statusCode int, reason string, w http.ResponseWriter, r *http.Request) { w.WriteHeader(statusCode) query := "error" if statusCode == 200 { query = "success" } errorRes := NewResult(query, r.URL.String()) errorRes.Items = append(errorRes.Items, ErrorJSON{Reason: reason, StatusCode: statusCode}) errorRes.StatusCode = statusCode data, _ := json.Marshal(errorRes) w.Write(data) }