Промежуточный коммит

This commit is contained in:
Александр Кирюхин 2023-11-05 22:22:54 +03:00
parent 7c96c41230
commit 7d8d260718
No known key found for this signature in database
GPG key ID: 7E27ABF5BF09B487
7 changed files with 133 additions and 25 deletions

View file

@ -77,6 +77,18 @@ paths:
$ref: '#/components/responses/teamResponse'
404:
$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:
post:
parameters:

View file

@ -30,6 +30,9 @@ type ServerInterface interface {
// (POST /teams)
PostTeams(ctx echo.Context) error
// (DELETE /teams/{teamID})
DeleteTeamsTeamID(ctx echo.Context, teamID int) error
// (GET /teams/{teamID})
GetTeamsTeamID(ctx echo.Context, teamID int) error
@ -88,6 +91,24 @@ func (w *ServerInterfaceWrapper) PostTeams(ctx echo.Context) error {
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.
func (w *ServerInterfaceWrapper) GetTeamsTeamID(ctx echo.Context) error {
var err error
@ -211,6 +232,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
router.GET(baseURL+"/games", wrapper.GetGames)
router.GET(baseURL+"/teams", wrapper.GetTeams)
router.POST(baseURL+"/teams", wrapper.PostTeams)
router.DELETE(baseURL+"/teams/:teamID", wrapper.DeleteTeamsTeamID)
router.GET(baseURL+"/teams/:teamID", wrapper.GetTeamsTeamID)
router.DELETE(baseURL+"/teams/:teamID/members", wrapper.DeleteTeamsTeamIDMembers)
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
var swaggerSpec = []string{
"H4sIAAAAAAAC/8xYXW+jOhD9K1e+9xGF9OOJt15VqqrbSnez2X2J8uDCNHU32Kw9tIoq/vtqjE2gQAJp",
"+vFUao/tc47PDENeWKzSTEmQaFj0wjT8zsHgvyoRYAfWaiUkPcRKIkikR55laxFzFEqGj0bZaRM/QMrp",
"KdMqA41uPaRcrOkBNxmwiBnUQq5YEbCMG/OsdNIxWQQWiNCQsGjh9qitWAZ+hbp7hBhZ0VyCOgc7sBIG",
"QX80/O3kaedsbkBLnsJ+5lVk0BahfsogQeyIyZQ0jpvWSs/cyBs0ilVSpyIkwgo0EU3BGL4awNNusY3v",
"ppOAibXICBOLGO2/4incCIMHkRAIqcX/j4Z7FrG/w20qhGWYCemEnwKe6TQHiWvNN32IEHh6DEk1cITk",
"Ajv9I5I+udM70GYwOQJ7a9e06QWsx6GBrxHjzpmVizp0bDpBkLGd5T2f2pFBTZqhLqHjzfvahI4YYxPK",
"6yPYpL869VmEgO4jQ+DmFDeqVNmLa9WrYXdUBI6iZVXlXItvY90I1ga5xp5ksu44wkUHDAWuhwpVxjaV",
"2OL0qNrqlTMuaUeWDa1KeEPufkax7v6HrCk16Xh7MXfu7sT1hbOsESN5fSjGbmPuBhjnWoPEucs9N3+n",
"1Bq4HFjQ25Mj0tKl5G52Vdq32PXB639DjHbaDtR2s12AZ+40kHlKi0vViC/PkAtZW9w0Tfdd9rE9tBR2",
"lMCAGYhzLXDzndTwbZT6JeAixweLgipjOeSliJgBY8pK4WtPJv4D95YR8l5ZbGUZYvKbTaaAPYE2ZaU9",
"mUwnU+KiMpA8EyxiZ5OTydT2kvhgYdiWp6zDYO1M2tjX0XXCInYFeGUDXjWTp9Np341XcWGrX6tLwaLF",
"kv4Pq4rcB2BuAw4B0G4FioCdT8/2r2w2y1bwTJkOfP8rUwPov6Y2b3jLDzNdn9dabf9goZoanR+gUXWb",
"4Qv9ub4s9l7r3MZZQ2qeAtryt3D5QCbdZgP60OZ3TlAT8nUWF8uvJEZYq+8JrAGhrculHa9Jc1s1xe+o",
"0GG2pZJ3fdm566facG+qfqCyn+c93yb1pd8P1wmNhtj4mDm4nnqIYfWLU/+lEdQbG9Yya/eptR+13P7F",
"cZhOxzPteON53irHQcQproX/vPWFxEwex2DMX27rYyOu/762G/PMRx5wX9UpX+fKmgPN1m2xpEQ3oJ98",
"+cj1mkUspH6rWBZ/AgAA//94VTXBchUAAA==",
"H4sIAAAAAAAC/8xYTW+rOBT9KyPPLFFIP1bsOqpUVdNKM5m8t4mycOE2dV+wefalVVTx35+usQkUSCBN",
"07cKsa/tc8798IU3Fqs0UxIkGha9MQ0/czD4t0oE2IG1WglJD7GSCBLpkWfZWsQchZLhs1F22sRPkHJ6",
"yrTKQKNbDykXa3rATQYsYga1kCtWBCzjxrwqnXRMFoEFIjQkLFq4PWorloFfoR6eIUZWNJegzsEOrIRB",
"0KeGv50875zNDWjJU9jPvLIM2iLUTxkkiB0xmZLGcdNa6Zkb+YBGsUrqVIREWIEmoikYw1cDeNottvbd",
"dBIwsRYZYWIRo/1XPIU7YfAgEgIhtfj/0vDIIvZnuE2FsDQzIZ3wXcArneYgca35pg8RAk+PIakGjpBc",
"YWf8iKRP7vQBtBlMjsDe2zVtegHridDA14hx58zKRR06NiNBUGC7kPd8akcGNWmGRgkdbz43TOiIMWFC",
"eX2EMOmvTn0hQkD3kSFwc7IbVaqs41r1apiPisBRtKyqnGvxbawbwdog19iTTDY6juDogKHA9VChStum",
"ElucHlVbvXLGJe3IsqFVCW+I72dk6/w/ZE2pScftxdy5uxPXF86yRozkdVKM3YG5G2Ccaw0S5y733PyD",
"UmvgcmBBb0+OSEuXkrvZVWnfYtcHr/+GGB1pO1DbzXYBnrnTQOYpLS5VI748Qy5kbXEzaLp92cf20FLY",
"UQIDZiDOtcDN/6SGb6PUDwFXOT5ZFFQZyyEvRcQMGFNWCl97MvEPuFtGyEdlsZVliMn/bDIF7AW0KSvt",
"2WQ6mRIXlYHkmWARu5icTaa2l8QnC8O2PGUdBhvOpI29jm4TFrEbwBtr8K6ZPJ9O+zxe2YWtfq0uBYsW",
"S/ofVhW5D8DcGhwCoN0KFAG7nF7sX9lslq3gmTId+P5VpgbQv01tPnDLDwu6vlhrtf2DhWpqdHmARpU3",
"wzf6ub0uylt8DQht5a7tuNVubq1tWGqeAtoiuHBZQaG6zQn0ps23naAm5/tcLpYtSS5b7QX7SGjsjNyT",
"k/tCf4e1K2yw3++rvv8TFTosM6mq31537vqlmba3Gp1Q2a+LPd8J9qXfN9fsjYbYeF87uC54iGH1Ua3f",
"aQT1zpq1grX71Np3O7d/cRym0/FMOy51z1vlOIg42Q2p0iaPYzDmD7f1sRHXPyHuxjzzlgf4qzrl93FZ",
"c6DZnS6WlOgG9IsvH7les4iF1FIWy+JXAAAA//+dkMP2VRYAAA==",
}
// GetSwagger returns the content of the embedded swagger specification file

View file

@ -19,18 +19,17 @@ export default () => {
}
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>
<NavbarBrand href="https://nquest.ru/">nQuest</NavbarBrand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<NavbarCollapse id="basic-navbar-nav" className="justify-content-end">
<Nav className="me-auto">
<Nav.Item>
<Link className="nav-link" to="/">Игры</Link>
<Nav.Link as={Link} className="nav-link" to="/">Игры</Nav.Link>
</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>
<Navbar.Text>

View file

@ -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 { UserProvider } from "../store/user";
import { ajax } from "../utils/fetch";
import { useState } from "react";
const userRoles = { captain: "Командир", member: "Участник" };
const userRoles = { captain: "Капитан", member: "Участник" };
export default () => {
const team = useLoaderData();
const navigate = useNavigate();
const {request, setRequest} = useState(false);
if (!team) {
return null;
@ -19,13 +23,43 @@ export default () => {
const member = team.members.find(tm => tm.user.id === user.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 (<>
<h1>{team.name}</h1>
<p>Создана: {team.createdAt}</p>
{!member && !inOtherTeam ? (<Button>Отправить заявку в команду</Button>) : null}
{member && member.role != "captain" ? (<Button>Выйти из команды</Button>) : null}
{!member && !inOtherTeam && !request ? (<Button onClick={sendRequest}>Отправить заявку в команду</Button>) : null}
{request ? (<p>Заявка в команду рассматривается</p>) : null}
{member && !isCaptain ? (<Button onClick={leaveTeam}>Выйти из команды</Button>) : null}
<h2>Участники</h2>
<Table>
@ -34,6 +68,7 @@ export default () => {
<th>Имя пользователя</th>
<th>Роль</th>
<th>Присоединился</th>
<th></th>
</tr>
</thead>
<tbody>
@ -42,9 +77,17 @@ export default () => {
<td>{tm.user.username}</td>
<td>{userRoles[tm.role]}</td>
<td>{tm.createdAt}</td>
<td>
{
isCaptain && tm.user.id!=user.id
? (<Button variant="outline-danger">Выгнать</Button>)
: null
}
</td>
</tr>
))}
</tbody>
</Table>
{isCaptain && (team.members.length == 1)?<Button variant="outline-danger" onClick={deleteTeam}>Удалить команду</Button>:null}
</>);
}

View file

@ -152,8 +152,8 @@ func main() {
api.RegisterHandlersWithBaseURL(codegen, handler, "/api")
e.FileFS("/", "index.html", distIndexHtml)
e.StaticFS("/", distDirFS)
e.FileFS("/*", "index.html", distIndexHtml)
// --[ System ]--
e.GET("/metrics", echoprometheus.NewHandler())

View file

@ -6,6 +6,7 @@ import (
"github.com/labstack/echo/v4"
"gitrepo.ru/neonxp/nquest/api"
"gitrepo.ru/neonxp/nquest/pkg/contextlib"
"gitrepo.ru/neonxp/nquest/pkg/models"
"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 {
team, err := t.TeamService.GetByID(ctx.Request().Context(), uint(teamID))
if err != nil {

View file

@ -71,6 +71,18 @@ func (ts *Team) GetByID(ctx context.Context, id uint) (*models.Team, error) {
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 {
return ts.DB.
WithContext(ctx).