Add getGame and listGames endpoints, client methods and initial components
This commit is contained in:
parent
ff455414f8
commit
08ab312f92
8 changed files with 136 additions and 8 deletions
|
@ -3,7 +3,9 @@ package api
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
|
@ -44,3 +46,22 @@ func (a *API) ListGames(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
JSON(w, games, 200)
|
||||
}
|
||||
|
||||
func (a *API) GetGame(w http.ResponseWriter, r *http.Request) {
|
||||
gameID, _ := strconv.Atoi(mux.Vars(r)["id"])
|
||||
user, _ := UserFromRequest(r)
|
||||
|
||||
game, err := a.App.GetGameForUser(gameID, user.ID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("game couldn't be fetch")
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if game == nil {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
JSON(w, game, 200)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,20 @@ func (a *App) AddMember(gameID, userID int, role string) (*model.GameMember, err
|
|||
return a.Store.Game().AddMember(gameID, userID, role)
|
||||
}
|
||||
|
||||
func (a *App) GetGameForUser(gameID, userID int) (*model.Game, error) {
|
||||
game, err := a.Store.Game().GetByID(gameID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ToDo: create a helper like userIsMember or
|
||||
// userHasPermissionToGame instead of checking ownership
|
||||
if game.UserID != userID {
|
||||
return nil, nil
|
||||
}
|
||||
return game, nil
|
||||
}
|
||||
|
||||
func (a *App) ListGames() ([]*model.Game, error) {
|
||||
games, err := a.Store.Game().List()
|
||||
if err == sql.ErrNoRows {
|
||||
|
|
|
@ -41,6 +41,7 @@ func (w *WebServer) RegisterRoutes(api *api.API) {
|
|||
apiRouter.HandleFunc("/users", api.Secured(api.CreateUser)).Methods("POST")
|
||||
apiRouter.HandleFunc("/games", api.Secured(api.CreateGame)).Methods("POST")
|
||||
apiRouter.HandleFunc("/games", api.Secured(api.ListGames)).Methods("GET")
|
||||
apiRouter.HandleFunc("/games/{id:[0-9]+}", api.Secured(api.GetGame)).Methods("GET")
|
||||
|
||||
staticFSSub, _ := fs.Sub(staticFS, "static")
|
||||
staticFSHandler := StaticFSHandler{http.FileServer(http.FS(staticFSSub))}
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
import * as log from './log'
|
||||
|
||||
|
||||
function tokenHeaders(headers) {
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
...headers
|
||||
}
|
||||
}
|
||||
|
||||
export class Client {
|
||||
login(username, password) {
|
||||
// ToDo: handle error
|
||||
|
@ -9,13 +20,39 @@ export class Client {
|
|||
if (r.status === 200) {
|
||||
return r.text()
|
||||
}
|
||||
console.error("INVALID")
|
||||
throw new Error("invalid credentials")
|
||||
log.error(`Got invalid response when trying to login. Code ${r.status}`)
|
||||
throw new Error(`${r.status} invalid response`)
|
||||
}).then(token => {
|
||||
localStorage.setItem('token', token)
|
||||
return token
|
||||
})
|
||||
}
|
||||
|
||||
listGames() {
|
||||
return fetch("/api/games", {
|
||||
method: 'GET',
|
||||
headers: tokenHeaders(),
|
||||
}).then(r => {
|
||||
if (r.status === 200) {
|
||||
return r.json()
|
||||
}
|
||||
log.error(`Got invalid response when trying to list games. Code ${r.status}`)
|
||||
throw new Error(`${r.status} invalid response`)
|
||||
})
|
||||
}
|
||||
|
||||
getGame(id) {
|
||||
return fetch(`/api/games/${id}`, {
|
||||
method: 'GET',
|
||||
headers: tokenHeaders(),
|
||||
}).then(r => {
|
||||
if (r.status === 200) {
|
||||
return r.json()
|
||||
}
|
||||
log.error(`Got invalid response when trying to get game ${id}. Code ${r.status}`)
|
||||
throw new Error(`${r.status} invalid response`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const client = new Client()
|
||||
|
|
|
@ -13,6 +13,7 @@ import { CssBaseline } from '@material-ui/core';
|
|||
import { TokenContext } from './context'
|
||||
import Login from './pages/login'
|
||||
import Home from './pages/home'
|
||||
import Game from './pages/game'
|
||||
|
||||
|
||||
const Secure = ({children}) => {
|
||||
|
@ -36,7 +37,13 @@ const App = () => {
|
|||
<Login />
|
||||
</Route>
|
||||
|
||||
<Route path="/">
|
||||
<Route path="/game/:gameId">
|
||||
<Secure>
|
||||
<Game />
|
||||
</Secure>
|
||||
</Route>
|
||||
|
||||
<Route exact path="/">
|
||||
<Secure>
|
||||
<Home />
|
||||
</Secure>
|
||||
|
|
|
@ -13,25 +13,25 @@ function nowStr() {
|
|||
return `${now.getFullYear()}${now.getDate()}${now.getMonth()}.${now.getHours()}${now.getMinutes()}${now.getSeconds()}`
|
||||
}
|
||||
|
||||
export function Debug(msg) {
|
||||
export function debug(msg) {
|
||||
if (level <= levelDebug) {
|
||||
console.log(`[DBG ${nowStr()}] ${msg}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function Info(msg) {
|
||||
export function info(msg) {
|
||||
if (level <= levelInfo) {
|
||||
console.log(`[INF ${nowStr()}] ${msg}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function Warn(msg) {
|
||||
export function warn(msg) {
|
||||
if (level <= levelWarn) {
|
||||
console.warn(`[WRN ${nowStr()}] ${msg}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function Error(msg) {
|
||||
export function error(msg) {
|
||||
if (level <= levelError) {
|
||||
console.error(`[ERR ${nowStr()}] ${msg}`)
|
||||
}
|
||||
|
|
22
webapp/src/pages/game.js
Normal file
22
webapp/src/pages/game.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
import client from '../client'
|
||||
|
||||
|
||||
const Game = () => {
|
||||
const [game, setGame] = useState({})
|
||||
const { gameId } = useParams()
|
||||
|
||||
useEffect(() => {
|
||||
client.getGame(gameId).then(game => setGame(game))
|
||||
}, [gameId])
|
||||
|
||||
return (
|
||||
<div class="game">
|
||||
<h1>{game.name}</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Game
|
|
@ -1,12 +1,36 @@
|
|||
import { useContext } from 'react'
|
||||
import {
|
||||
useContext,
|
||||
useEffect,
|
||||
useState
|
||||
} from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import {
|
||||
Box,
|
||||
Button
|
||||
} from '@material-ui/core';
|
||||
|
||||
import { TokenContext } from '../context'
|
||||
import client from '../client'
|
||||
|
||||
|
||||
const GameList = () => {
|
||||
const [games, setGames] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
client.listGames().then(gameList => setGames(gameList))
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div class="game-list">
|
||||
<h3 class="game-list-title">Game List</h3>
|
||||
|
||||
<ul>
|
||||
{games.map(game => (<li><Link to={`/game/${game.id}`}>{game.name}</Link></li>))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Home = () => {
|
||||
const { token, setToken } = useContext(TokenContext)
|
||||
|
||||
|
@ -20,6 +44,8 @@ const Home = () => {
|
|||
<Box>
|
||||
<h1>Home</h1>
|
||||
|
||||
<GameList />
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
|
|
Loading…
Reference in a new issue