Промежуточный коммит
This commit is contained in:
parent
7c96c41230
commit
7d8d260718
7 changed files with 133 additions and 25 deletions
|
@ -77,6 +77,18 @@ paths:
|
||||||
$ref: '#/components/responses/teamResponse'
|
$ref: '#/components/responses/teamResponse'
|
||||||
404:
|
404:
|
||||||
$ref: '#/components/responses/errorResponse'
|
$ref: '#/components/responses/errorResponse'
|
||||||
|
delete:
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: teamID
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
204:
|
||||||
|
description: ''
|
||||||
|
403:
|
||||||
|
$ref: '#/components/responses/errorResponse'
|
||||||
/teams/{teamID}/members:
|
/teams/{teamID}/members:
|
||||||
post:
|
post:
|
||||||
parameters:
|
parameters:
|
||||||
|
|
|
@ -30,6 +30,9 @@ type ServerInterface interface {
|
||||||
// (POST /teams)
|
// (POST /teams)
|
||||||
PostTeams(ctx echo.Context) error
|
PostTeams(ctx echo.Context) error
|
||||||
|
|
||||||
|
// (DELETE /teams/{teamID})
|
||||||
|
DeleteTeamsTeamID(ctx echo.Context, teamID int) error
|
||||||
|
|
||||||
// (GET /teams/{teamID})
|
// (GET /teams/{teamID})
|
||||||
GetTeamsTeamID(ctx echo.Context, teamID int) error
|
GetTeamsTeamID(ctx echo.Context, teamID int) error
|
||||||
|
|
||||||
|
@ -88,6 +91,24 @@ func (w *ServerInterfaceWrapper) PostTeams(ctx echo.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteTeamsTeamID converts echo context to params.
|
||||||
|
func (w *ServerInterfaceWrapper) DeleteTeamsTeamID(ctx echo.Context) error {
|
||||||
|
var err error
|
||||||
|
// ------------- Path parameter "teamID" -------------
|
||||||
|
var teamID int
|
||||||
|
|
||||||
|
err = runtime.BindStyledParameterWithLocation("simple", false, "teamID", runtime.ParamLocationPath, ctx.Param("teamID"), &teamID)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter teamID: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Set(CookieAuthScopes, []string{})
|
||||||
|
|
||||||
|
// Invoke the callback with all the unmarshaled arguments
|
||||||
|
err = w.Handler.DeleteTeamsTeamID(ctx, teamID)
|
||||||
|
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
|
||||||
|
@ -211,6 +232,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.POST(baseURL+"/teams", wrapper.PostTeams)
|
||||||
|
router.DELETE(baseURL+"/teams/:teamID", wrapper.DeleteTeamsTeamID)
|
||||||
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)
|
||||||
|
@ -224,22 +246,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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",
|
||||||
"+cj1mkUspH6rWBZ/AgAA//94VTXBchUAAA==",
|
"c6DZnS6WlOgG9IsvH7les4iF1FIWy+JXAAAA//+dkMP2VRYAAA==",
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSwagger returns the content of the embedded swagger specification file
|
// GetSwagger returns the content of the embedded swagger specification file
|
||||||
|
|
|
@ -19,18 +19,17 @@ export default () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<Navbar className="navbar navbar-expand-lg bg-primary" data-bs-theme="dark">
|
<Navbar expand="lg" className="navbar navbar-expand-lg bg-primary" data-bs-theme="dark">
|
||||||
<Container>
|
<Container>
|
||||||
<NavbarBrand href="https://nquest.ru/">nQuest</NavbarBrand>
|
<NavbarBrand href="https://nquest.ru/">nQuest</NavbarBrand>
|
||||||
<Navbar.Toggle aria-controls="basic-navbar-nav" />
|
<Navbar.Toggle aria-controls="basic-navbar-nav" />
|
||||||
<NavbarCollapse id="basic-navbar-nav" className="justify-content-end">
|
<NavbarCollapse id="basic-navbar-nav" className="justify-content-end">
|
||||||
<Nav className="me-auto">
|
<Nav className="me-auto">
|
||||||
<Nav.Item>
|
<Nav.Item>
|
||||||
<Link className="nav-link" to="/">Игры</Link>
|
<Nav.Link as={Link} className="nav-link" to="/">Игры</Nav.Link>
|
||||||
</Nav.Item>
|
</Nav.Item>
|
||||||
|
|
||||||
<Nav.Item>
|
<Nav.Item>
|
||||||
<Link className="nav-link" to="/teams">Команды</Link>
|
<Nav.Link as={Link} className="nav-link" to="/teams">Команды</Nav.Link>
|
||||||
</Nav.Item>
|
</Nav.Item>
|
||||||
</Nav>
|
</Nav>
|
||||||
<Navbar.Text>
|
<Navbar.Text>
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import { useLoaderData, useRouteLoaderData } from "react-router-dom";
|
import { useLoaderData, useNavigate, useRouteLoaderData } from "react-router-dom";
|
||||||
import { Button, Table } from "react-bootstrap";
|
import { Button, Table } from "react-bootstrap";
|
||||||
import { UserProvider } from "../store/user";
|
import { UserProvider } from "../store/user";
|
||||||
|
import { ajax } from "../utils/fetch";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
const userRoles = { captain: "Командир", member: "Участник" };
|
const userRoles = { captain: "Капитан", member: "Участник" };
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const team = useLoaderData();
|
const team = useLoaderData();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const {request, setRequest} = useState(false);
|
||||||
|
|
||||||
if (!team) {
|
if (!team) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -19,13 +23,43 @@ export default () => {
|
||||||
|
|
||||||
const member = team.members.find(tm => tm.user.id === user.id);
|
const member = team.members.find(tm => tm.user.id === user.id);
|
||||||
const inOtherTeam = user.team && user.team.td != team.id;
|
const inOtherTeam = user.team && user.team.td != team.id;
|
||||||
|
const isCaptain = member && user.team.role == "captain";
|
||||||
|
|
||||||
|
const sendRequest = () => {
|
||||||
|
ajax(`/api/teams/${team.id}/members`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
}).then(() => setRequest(true));
|
||||||
|
};
|
||||||
|
const leaveTeam = () => {
|
||||||
|
ajax(`/api/teams/${team.id}/members`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
}).then(() => navigate("/teams"));
|
||||||
|
};
|
||||||
|
const deleteTeam = () => {
|
||||||
|
ajax(`/api/teams/${team.id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
}).then(() => navigate("/teams"));
|
||||||
|
};
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<h1>{team.name}</h1>
|
<h1>{team.name}</h1>
|
||||||
<p>Создана: {team.createdAt}</p>
|
<p>Создана: {team.createdAt}</p>
|
||||||
|
|
||||||
{!member && !inOtherTeam ? (<Button>Отправить заявку в команду</Button>) : null}
|
{!member && !inOtherTeam && !request ? (<Button onClick={sendRequest}>Отправить заявку в команду</Button>) : null}
|
||||||
{member && member.role != "captain" ? (<Button>Выйти из команды</Button>) : null}
|
{request ? (<p>Заявка в команду рассматривается</p>) : null}
|
||||||
|
{member && !isCaptain ? (<Button onClick={leaveTeam}>Выйти из команды</Button>) : null}
|
||||||
|
|
||||||
<h2>Участники</h2>
|
<h2>Участники</h2>
|
||||||
<Table>
|
<Table>
|
||||||
|
@ -34,6 +68,7 @@ export default () => {
|
||||||
<th>Имя пользователя</th>
|
<th>Имя пользователя</th>
|
||||||
<th>Роль</th>
|
<th>Роль</th>
|
||||||
<th>Присоединился</th>
|
<th>Присоединился</th>
|
||||||
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -42,9 +77,17 @@ export default () => {
|
||||||
<td>{tm.user.username}</td>
|
<td>{tm.user.username}</td>
|
||||||
<td>{userRoles[tm.role]}</td>
|
<td>{userRoles[tm.role]}</td>
|
||||||
<td>{tm.createdAt}</td>
|
<td>{tm.createdAt}</td>
|
||||||
|
<td>
|
||||||
|
{
|
||||||
|
isCaptain && tm.user.id!=user.id
|
||||||
|
? (<Button variant="outline-danger">Выгнать</Button>)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</Table>
|
</Table>
|
||||||
|
{isCaptain && (team.members.length == 1)?<Button variant="outline-danger" onClick={deleteTeam}>Удалить команду</Button>:null}
|
||||||
</>);
|
</>);
|
||||||
}
|
}
|
2
main.go
2
main.go
|
@ -152,8 +152,8 @@ func main() {
|
||||||
|
|
||||||
api.RegisterHandlersWithBaseURL(codegen, handler, "/api")
|
api.RegisterHandlersWithBaseURL(codegen, handler, "/api")
|
||||||
|
|
||||||
e.FileFS("/", "index.html", distIndexHtml)
|
|
||||||
e.StaticFS("/", distDirFS)
|
e.StaticFS("/", distDirFS)
|
||||||
|
e.FileFS("/*", "index.html", distIndexHtml)
|
||||||
|
|
||||||
// --[ System ]--
|
// --[ System ]--
|
||||||
e.GET("/metrics", echoprometheus.NewHandler())
|
e.GET("/metrics", echoprometheus.NewHandler())
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"gitrepo.ru/neonxp/nquest/api"
|
"gitrepo.ru/neonxp/nquest/api"
|
||||||
"gitrepo.ru/neonxp/nquest/pkg/contextlib"
|
"gitrepo.ru/neonxp/nquest/pkg/contextlib"
|
||||||
|
"gitrepo.ru/neonxp/nquest/pkg/models"
|
||||||
"gitrepo.ru/neonxp/nquest/pkg/service"
|
"gitrepo.ru/neonxp/nquest/pkg/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -92,6 +93,25 @@ func (t *Team) PostTeams(ctx echo.Context) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Team) DeleteTeamsTeamID(ctx echo.Context, teamID int) error {
|
||||||
|
user := contextlib.GetUser(ctx)
|
||||||
|
if user.Team == nil || user.Team.TeamID != uint(teamID) || user.Team.Role != models.Captain {
|
||||||
|
return ctx.JSON(http.StatusForbidden, api.ErrorResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
Message: "Нельзя удалить не свою команду",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.TeamService.Delete(ctx.Request().Context(), uint(teamID)); err != nil {
|
||||||
|
return ctx.JSON(http.StatusForbidden, api.ErrorResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.NoContent(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
|
|
@ -71,6 +71,18 @@ func (ts *Team) GetByID(ctx context.Context, id uint) (*models.Team, error) {
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ts *Team) Delete(ctx context.Context, id uint) error {
|
||||||
|
if err := ts.DB.WithContext(ctx).Delete(&models.Team{}, id).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return ErrTeamNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ts *Team) Request(ctx context.Context, team *models.Team, user *models.User) error {
|
func (ts *Team) Request(ctx context.Context, team *models.Team, user *models.User) error {
|
||||||
return ts.DB.
|
return ts.DB.
|
||||||
WithContext(ctx).
|
WithContext(ctx).
|
||||||
|
|
Loading…
Reference in a new issue