From 7d8d260718a0369a459cd5c542e6dd88f0f7c0e6 Mon Sep 17 00:00:00 2001 From: NeonXP Date: Sun, 5 Nov 2023 22:22:54 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=BC=D0=B5=D0=B6=D1=83?= =?UTF-8?q?=D1=82=D0=BE=D1=87=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BE=D0=BC=D0=BC?= =?UTF-8?q?=D0=B8=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/openapi.yaml | 12 +++++++ api/server.go | 54 +++++++++++++++++++++--------- frontend/src/components/Layout.jsx | 7 ++-- frontend/src/pages/Team.jsx | 51 +++++++++++++++++++++++++--- main.go | 2 +- pkg/controller/team.go | 20 +++++++++++ pkg/service/team.go | 12 +++++++ 7 files changed, 133 insertions(+), 25 deletions(-) diff --git a/api/openapi.yaml b/api/openapi.yaml index 7c368ff..005f374 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -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: diff --git a/api/server.go b/api/server.go index 33de6d0..9d7d281 100644 --- a/api/server.go +++ b/api/server.go @@ -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 diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index 3286ba9..476c5ec 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -19,18 +19,17 @@ export default () => { } return (<> - + nQuest diff --git a/frontend/src/pages/Team.jsx b/frontend/src/pages/Team.jsx index 9a72e4c..14baa69 100644 --- a/frontend/src/pages/Team.jsx +++ b/frontend/src/pages/Team.jsx @@ -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 (<>

{team.name}

Создана: {team.createdAt}

- {!member && !inOtherTeam ? () : null} - {member && member.role != "captain" ? () : null} + {!member && !inOtherTeam && !request ? () : null} + {request ? (

Заявка в команду рассматривается

) : null} + {member && !isCaptain ? () : null}

Участники

@@ -34,6 +68,7 @@ export default () => { + @@ -42,9 +77,17 @@ export default () => { + ))}
Имя пользователя Роль Присоединился
{tm.user.username} {userRoles[tm.role]} {tm.createdAt} + { + isCaptain && tm.user.id!=user.id + ? () + : null + } +
+ {isCaptain && (team.members.length == 1)?:null} ); } \ No newline at end of file diff --git a/main.go b/main.go index e7e3cd7..180d93b 100644 --- a/main.go +++ b/main.go @@ -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()) diff --git a/pkg/controller/team.go b/pkg/controller/team.go index f2ea036..4dc3423 100644 --- a/pkg/controller/team.go +++ b/pkg/controller/team.go @@ -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 { diff --git a/pkg/service/team.go b/pkg/service/team.go index c3db3d1..bcf1e35 100644 --- a/pkg/service/team.go +++ b/pkg/service/team.go @@ -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).