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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -44,3 +46,22 @@ func (a *API) ListGames(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
JSON(w, games, 200)
|
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)
|
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) {
|
func (a *App) ListGames() ([]*model.Game, error) {
|
||||||
games, err := a.Store.Game().List()
|
games, err := a.Store.Game().List()
|
||||||
if err == sql.ErrNoRows {
|
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("/users", api.Secured(api.CreateUser)).Methods("POST")
|
||||||
apiRouter.HandleFunc("/games", api.Secured(api.CreateGame)).Methods("POST")
|
apiRouter.HandleFunc("/games", api.Secured(api.CreateGame)).Methods("POST")
|
||||||
apiRouter.HandleFunc("/games", api.Secured(api.ListGames)).Methods("GET")
|
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")
|
staticFSSub, _ := fs.Sub(staticFS, "static")
|
||||||
staticFSHandler := StaticFSHandler{http.FileServer(http.FS(staticFSSub))}
|
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 {
|
export class Client {
|
||||||
login(username, password) {
|
login(username, password) {
|
||||||
// ToDo: handle error
|
// ToDo: handle error
|
||||||
|
@ -9,13 +20,39 @@ export class Client {
|
||||||
if (r.status === 200) {
|
if (r.status === 200) {
|
||||||
return r.text()
|
return r.text()
|
||||||
}
|
}
|
||||||
console.error("INVALID")
|
log.error(`Got invalid response when trying to login. Code ${r.status}`)
|
||||||
throw new Error("invalid credentials")
|
throw new Error(`${r.status} invalid response`)
|
||||||
}).then(token => {
|
}).then(token => {
|
||||||
localStorage.setItem('token', token)
|
localStorage.setItem('token', token)
|
||||||
return 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()
|
const client = new Client()
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { CssBaseline } from '@material-ui/core';
|
||||||
import { TokenContext } from './context'
|
import { TokenContext } from './context'
|
||||||
import Login from './pages/login'
|
import Login from './pages/login'
|
||||||
import Home from './pages/home'
|
import Home from './pages/home'
|
||||||
|
import Game from './pages/game'
|
||||||
|
|
||||||
|
|
||||||
const Secure = ({children}) => {
|
const Secure = ({children}) => {
|
||||||
|
@ -36,7 +37,13 @@ const App = () => {
|
||||||
<Login />
|
<Login />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="/">
|
<Route path="/game/:gameId">
|
||||||
|
<Secure>
|
||||||
|
<Game />
|
||||||
|
</Secure>
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route exact path="/">
|
||||||
<Secure>
|
<Secure>
|
||||||
<Home />
|
<Home />
|
||||||
</Secure>
|
</Secure>
|
||||||
|
|
|
@ -13,25 +13,25 @@ function nowStr() {
|
||||||
return `${now.getFullYear()}${now.getDate()}${now.getMonth()}.${now.getHours()}${now.getMinutes()}${now.getSeconds()}`
|
return `${now.getFullYear()}${now.getDate()}${now.getMonth()}.${now.getHours()}${now.getMinutes()}${now.getSeconds()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Debug(msg) {
|
export function debug(msg) {
|
||||||
if (level <= levelDebug) {
|
if (level <= levelDebug) {
|
||||||
console.log(`[DBG ${nowStr()}] ${msg}`)
|
console.log(`[DBG ${nowStr()}] ${msg}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Info(msg) {
|
export function info(msg) {
|
||||||
if (level <= levelInfo) {
|
if (level <= levelInfo) {
|
||||||
console.log(`[INF ${nowStr()}] ${msg}`)
|
console.log(`[INF ${nowStr()}] ${msg}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Warn(msg) {
|
export function warn(msg) {
|
||||||
if (level <= levelWarn) {
|
if (level <= levelWarn) {
|
||||||
console.warn(`[WRN ${nowStr()}] ${msg}`)
|
console.warn(`[WRN ${nowStr()}] ${msg}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Error(msg) {
|
export function error(msg) {
|
||||||
if (level <= levelError) {
|
if (level <= levelError) {
|
||||||
console.error(`[ERR ${nowStr()}] ${msg}`)
|
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 {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button
|
Button
|
||||||
} from '@material-ui/core';
|
} from '@material-ui/core';
|
||||||
|
|
||||||
import { TokenContext } from '../context'
|
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 Home = () => {
|
||||||
const { token, setToken } = useContext(TokenContext)
|
const { token, setToken } = useContext(TokenContext)
|
||||||
|
|
||||||
|
@ -20,6 +44,8 @@ const Home = () => {
|
||||||
<Box>
|
<Box>
|
||||||
<h1>Home</h1>
|
<h1>Home</h1>
|
||||||
|
|
||||||
|
<GameList />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
|
|
Loading…
Reference in a new issue