Создание команды

This commit is contained in:
Александр Кирюхин 2023-11-02 23:38:50 +03:00
parent 9a705f5b03
commit 7c96c41230
No known key found for this signature in database
GPG key ID: 7E27ABF5BF09B487
9 changed files with 117 additions and 83 deletions

View file

@ -49,6 +49,21 @@ paths:
$ref: '#/components/responses/teamsListResponse' $ref: '#/components/responses/teamsListResponse'
403: 403:
$ref: '#/components/responses/errorResponse' $ref: '#/components/responses/errorResponse'
post:
requestBody:
content:
'application/json':
schema:
type: object
properties:
name:
type: string
required: [ name ]
responses:
200:
$ref: '#/components/responses/teamResponse'
404:
$ref: '#/components/responses/errorResponse'
/teams/{teamID}: /teams/{teamID}:
get: get:
parameters: parameters:

View file

@ -27,6 +27,9 @@ type ServerInterface interface {
// (GET /teams) // (GET /teams)
GetTeams(ctx echo.Context) error GetTeams(ctx echo.Context) error
// (POST /teams)
PostTeams(ctx echo.Context) error
// (GET /teams/{teamID}) // (GET /teams/{teamID})
GetTeamsTeamID(ctx echo.Context, teamID int) error GetTeamsTeamID(ctx echo.Context, teamID int) error
@ -74,6 +77,17 @@ func (w *ServerInterfaceWrapper) GetTeams(ctx echo.Context) error {
return err return err
} }
// PostTeams converts echo context to params.
func (w *ServerInterfaceWrapper) PostTeams(ctx echo.Context) error {
var err error
ctx.Set(CookieAuthScopes, []string{})
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.PostTeams(ctx)
return err
}
// GetTeamsTeamID converts echo context to params. // GetTeamsTeamID converts echo context to params.
func (w *ServerInterfaceWrapper) GetTeamsTeamID(ctx echo.Context) error { func (w *ServerInterfaceWrapper) GetTeamsTeamID(ctx echo.Context) error {
var err error var err error
@ -196,6 +210,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
router.GET(baseURL+"/games", wrapper.GetGames) router.GET(baseURL+"/games", wrapper.GetGames)
router.GET(baseURL+"/teams", wrapper.GetTeams) router.GET(baseURL+"/teams", wrapper.GetTeams)
router.POST(baseURL+"/teams", wrapper.PostTeams)
router.GET(baseURL+"/teams/:teamID", wrapper.GetTeamsTeamID) router.GET(baseURL+"/teams/:teamID", wrapper.GetTeamsTeamID)
router.DELETE(baseURL+"/teams/:teamID/members", wrapper.DeleteTeamsTeamIDMembers) router.DELETE(baseURL+"/teams/:teamID/members", wrapper.DeleteTeamsTeamIDMembers)
router.POST(baseURL+"/teams/:teamID/members", wrapper.PostTeamsTeamIDMembers) router.POST(baseURL+"/teams/:teamID/members", wrapper.PostTeamsTeamIDMembers)
@ -209,22 +224,22 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object // Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{ var swaggerSpec = []string{
"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",
"//8/OTdEThQAAA==", "+cj1mkUspH6rWBZ/AgAA//94VTXBchUAAA==",
} }
// GetSwagger returns the content of the embedded swagger specification file // GetSwagger returns the content of the embedded swagger specification file

View file

@ -103,6 +103,11 @@ type Register struct {
Username string `json:"username"` Username string `json:"username"`
} }
// PostTeamsJSONBody defines parameters for PostTeams.
type PostTeamsJSONBody struct {
Name string `json:"name"`
}
// DeleteTeamsTeamIDMembersJSONBody defines parameters for DeleteTeamsTeamIDMembers. // DeleteTeamsTeamIDMembersJSONBody defines parameters for DeleteTeamsTeamIDMembers.
type DeleteTeamsTeamIDMembersJSONBody = interface{} type DeleteTeamsTeamIDMembersJSONBody = interface{}
@ -120,6 +125,9 @@ type PostUserRegisterJSONBody struct {
Username string `json:"username"` Username string `json:"username"`
} }
// PostTeamsJSONRequestBody defines body for PostTeams for application/json ContentType.
type PostTeamsJSONRequestBody PostTeamsJSONBody
// DeleteTeamsTeamIDMembersJSONRequestBody defines body for DeleteTeamsTeamIDMembers for application/json ContentType. // DeleteTeamsTeamIDMembersJSONRequestBody defines body for DeleteTeamsTeamIDMembers for application/json ContentType.
type DeleteTeamsTeamIDMembersJSONRequestBody = DeleteTeamsTeamIDMembersJSONBody type DeleteTeamsTeamIDMembersJSONRequestBody = DeleteTeamsTeamIDMembersJSONBody

View file

@ -1,5 +1,5 @@
import { Link, Outlet, useLoaderData } from "react-router-dom"; import { Link, Outlet, useLoaderData } from "react-router-dom";
import { Button } from "react-bootstrap"; import { Button, Nav, Container, NavbarBrand, NavbarToggle, Navbar, NavbarCollapse, ButtonGroup } from "react-bootstrap";
import { UserProvider } from "../store/user"; import { UserProvider } from "../store/user";
import { useEffect } from "react"; import { useEffect } from "react";
import { ajax } from "../utils/fetch"; import { ajax } from "../utils/fetch";
@ -15,53 +15,48 @@ export default () => {
ajax("/api/user/logout", { ajax("/api/user/logout", {
method: "POST", method: "POST",
}). }).
then(() => setUser(null)) then(() => setUser(null))
} }
return (<> return (<>
<nav className="navbar navbar-expand-lg bg-primary" data-bs-theme="dark"> <Navbar className="navbar navbar-expand-lg bg-primary" data-bs-theme="dark">
<div className="container"> <Container>
<a className="navbar-brand" href="https://nquest.ru/">nQuest</a> <NavbarBrand href="https://nquest.ru/">nQuest</NavbarBrand>
<button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" <Navbar.Toggle aria-controls="basic-navbar-nav" />
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <NavbarCollapse id="basic-navbar-nav" className="justify-content-end">
<span className="navbar-toggler-icon"></span> <Nav className="me-auto">
</button> <Nav.Item>
<div className="collapse navbar-collapse" id="navbarSupportedContent">
<ul className="navbar-nav me-auto mb-2 mb-lg-0">
<li className="nav-item">
<Link className="nav-link" to="/">Игры</Link> <Link className="nav-link" to="/">Игры</Link>
</li> </Nav.Item>
<li className="nav-item"> <Nav.Item>
<Link className="nav-link" to="/teams">Команды</Link> <Link className="nav-link" to="/teams">Команды</Link>
</li> </Nav.Item>
</ul> </Nav>
<div className="d-flex"> <Navbar.Text>
{user ? ( {user ? (
<> <>
<span className="navbar-text me-2">
{user.username}&nbsp; {user.username}&nbsp;
{user.team ? ( {user.team ? (
<>(<Link to={`teams/${user.team.id}`}>{user.team.name}</Link>)</> <>(<Link to={`teams/${user.team.id}`}>{user.team.name}</Link>)</>
) : ( ) : (
<>(без команды)</> <>(без команды)</>
)} )}&nbsp;
</span> <Button type="button" variant="outline-success" onClick={logout}>Выход</Button>
<Button type="button" variant="outline-success" onClick={logout}>Выход</Button> </>
</> ) : (
) : ( <ButtonGroup>
<div className="btn-group"> <Link className="btn btn-success" to="login">Вход</Link>
<Link className="btn btn-success" to="login">Вход</Link> <Link className="btn btn-outline-success" to="register">Регистрация</Link>
<Link className="btn btn-outline-success" to="register">Регистрация</Link> </ButtonGroup>
</div> )}
)} </Navbar.Text>
</div> </NavbarCollapse>
</div> </Container>
</div> </Navbar>
</nav>
<div className="container my-5"> <Container>
<Outlet /> <Outlet />
</div> </Container>
</>); </>);
} }

View file

@ -7,7 +7,7 @@ export default () => {
return (<> return (<>
<h1 className="mb-4">Текущие игры</h1> <h1 className="mb-4">Текущие игры</h1>
{games.map(game => ( {games && games.map(game => (
<> <>
<h3>{game.title}</h3> <h3>{game.title}</h3>
<Table className="table table-bordered mb-4"> <Table className="table table-bordered mb-4">
@ -43,6 +43,6 @@ export default () => {
</Table> </Table>
</> </>
))} ))}
{games.length == 0 ? (<strong>Игр пока не анонсировано</strong>) : null} {!games ? (<strong>Игр пока не анонсировано</strong>) : null}
</>); </>);
} }

View file

@ -10,20 +10,16 @@ export default () => {
const onCreate = (e) => { const onCreate = (e) => {
e.preventDefault(); e.preventDefault();
ajax("/api/teams/", { ajax("/api/teams", {
method: "POST", method: "POST",
headers: { headers: {
'Accept': 'application/json', 'Accept': 'application/json',
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ name }) body: JSON.stringify({ name })
}).then(({team}) => { }).then((team) => {
if (!team) { navigate(`/teams/${team.id}`)
setError("Ошибка создания команды") }).catch(e => setError(e.message))
return
}
navigate(`/teams/${team.ID}`)
})
} }
return (<> return (<>
<h1>Создание команды</h1> <h1>Создание команды</h1>

View file

@ -7,8 +7,14 @@ export default () => {
if (!teams) { if (!teams) {
return null return null
} }
const { user } = UserProvider.useContainer();
return (<> return (<>
<h1>Команды</h1> <h1>Команды</h1>
{user && !user.team
? (<p>Вы не состоите в командах. <Link to="/teams/new">Создать свою команду.</Link></p>)
: null}
<Table> <Table>
<thead> <thead>
<tr> <tr>

View file

@ -72,6 +72,26 @@ func (t *Team) PostTeamsTeamIDMembers(ctx echo.Context, teamID int) error {
return t.getTeamResponse(ctx, teamID) return t.getTeamResponse(ctx, teamID)
} }
func (t *Team) PostTeams(ctx echo.Context) error {
req := &api.PostTeamsJSONRequestBody{}
if err := ctx.Bind(req); err != nil {
return err
}
user := contextlib.GetUser(ctx)
team, err := t.TeamService.Create(ctx.Request().Context(), req.Name, user)
if err != nil {
return err
}
return ctx.JSON(http.StatusCreated, api.TeamView{
Id: int(team.ID),
Name: team.Name,
CreatedAt: team.CreatedAt.Format("02.01.06"),
})
}
func (t *Team) getTeamResponse(ctx echo.Context, teamID int) error { func (t *Team) getTeamResponse(ctx echo.Context, teamID int) error {
team, err := t.TeamService.GetByID(ctx.Request().Context(), uint(teamID)) team, err := t.TeamService.GetByID(ctx.Request().Context(), uint(teamID))
if err != nil { if err != nil {

View file

@ -1,21 +0,0 @@
{{ template "header" . }}
<h1>Новая команда</h1>
<form method="POST">
<div class="col-lg-8 px-0">
{{ if .Error }}
<div class="alert alert-danger" role="alert">{{ .Error }}</div>
{{ end }}
<div class="mb-3 row">
<label for="staticEmail" class="col-sm-4 col-form-label">Название команды</label>
<div class="col-sm-8">
<input type="text" class="form-control" name="name" value="{{ .Name }}">
</div>
</div>
<div class="mb-3 row">
<div class="col-sm-8 offset-md-4">
<input type="submit" class="form-control btn btn-primary" value="Регистрация">
</div>
</div>
</div>
</form>
{{ template "footer" . }}