Поддержка ролей пользователя
This commit is contained in:
parent
8f6748d2ca
commit
d0fce2b2fe
18 changed files with 570 additions and 148 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
*.sql
|
9
Makefile
9
Makefile
|
@ -2,10 +2,9 @@
|
||||||
db:
|
db:
|
||||||
docker run --name nquest-db -e POSTGRES_DB=nquest -e POSTGRES_USER=nquest -e POSTGRES_PASSWORD=nquest -p 5432:5432 -d postgres
|
docker run --name nquest-db -e POSTGRES_DB=nquest -e POSTGRES_USER=nquest -e POSTGRES_PASSWORD=nquest -p 5432:5432 -d postgres
|
||||||
|
|
||||||
.PHONY: gen-api
|
.PHONY: generate
|
||||||
gen-api:
|
generate:
|
||||||
oapi-codegen -generate server,spec -package api -o api/server.go api/openapi.yaml
|
go generate ./...
|
||||||
oapi-codegen -generate types -package api -o api/types.go api/openapi.yaml
|
|
||||||
|
|
||||||
.PHONY: build-front
|
.PHONY: build-front
|
||||||
build-front:
|
build-front:
|
||||||
|
@ -13,4 +12,4 @@ build-front:
|
||||||
|
|
||||||
.PHONY: dev-front
|
.PHONY: dev-front
|
||||||
dev-front:
|
dev-front:
|
||||||
cd frontend & npm run dev
|
cd frontend; npm run dev
|
4
api/doc.go
Normal file
4
api/doc.go
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -generate server,spec -package api -o ./server.go ./openapi.yaml
|
||||||
|
//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -generate types -package api -o ./types.go ./openapi.yaml
|
|
@ -2,10 +2,28 @@ package api
|
||||||
|
|
||||||
import "gitrepo.ru/neonxp/nquest/pkg/models"
|
import "gitrepo.ru/neonxp/nquest/pkg/models"
|
||||||
|
|
||||||
var MapRole = map[models.Role]UserTeamRole{
|
var MapTeamRole = map[models.Role]UserTeamRole{
|
||||||
models.Captain: Captain,
|
models.Captain: Captain,
|
||||||
models.Member: Member,
|
models.Member: Member,
|
||||||
}
|
}
|
||||||
|
var MapTeamRoleReverse = map[UserTeamRole]models.Role{
|
||||||
|
Captain: models.Captain,
|
||||||
|
Member: models.Member,
|
||||||
|
}
|
||||||
|
|
||||||
|
var MapUserRole = map[models.UserRole]UserRole{
|
||||||
|
models.RoleNotVerified: NotVerified,
|
||||||
|
models.RoleUser: User,
|
||||||
|
models.RoleCreator: Creator,
|
||||||
|
models.RoleAdmin: Admin,
|
||||||
|
}
|
||||||
|
|
||||||
|
var MapUserRoleReverse = map[UserRole]models.UserRole{
|
||||||
|
NotVerified: models.RoleNotVerified,
|
||||||
|
User: models.RoleUser,
|
||||||
|
Creator: models.RoleCreator,
|
||||||
|
Admin: models.RoleAdmin,
|
||||||
|
}
|
||||||
|
|
||||||
func MapUserTeam(team *models.TeamMember) *UserTeam {
|
func MapUserTeam(team *models.TeamMember) *UserTeam {
|
||||||
if team == nil || team.Team == nil {
|
if team == nil || team.Team == nil {
|
||||||
|
@ -15,6 +33,6 @@ func MapUserTeam(team *models.TeamMember) *UserTeam {
|
||||||
return &UserTeam{
|
return &UserTeam{
|
||||||
Id: int(team.Team.ID),
|
Id: int(team.Team.ID),
|
||||||
Name: team.Team.Name,
|
Name: team.Team.Name,
|
||||||
Role: MapRole[team.Role],
|
Role: MapTeamRole[team.Role],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
openapi: "3.1.0"
|
openapi: "3.1.0"
|
||||||
|
|
||||||
info:
|
info:
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
title: nQuest
|
title: nQuest
|
||||||
|
|
||||||
servers:
|
servers:
|
||||||
- url: /api
|
- url: /api
|
||||||
|
|
||||||
paths:
|
paths:
|
||||||
# User routes
|
# User routes
|
||||||
/user:
|
/user:
|
||||||
|
@ -97,6 +100,17 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
required: true
|
required: true
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
members:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
required: [ members ]
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: '#/components/responses/teamResponse'
|
$ref: '#/components/responses/teamResponse'
|
||||||
|
@ -109,12 +123,46 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
required: true
|
required: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
$ref: '#/components/responses/teamResponse'
|
||||||
|
404:
|
||||||
|
$ref: '#/components/responses/errorResponse'
|
||||||
|
/teams/{teamID}/requests/{userID}:
|
||||||
|
post:
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: teamID
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
required: true
|
||||||
|
- in: path
|
||||||
|
name: userID
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
required: true
|
||||||
requestBody:
|
requestBody:
|
||||||
content:
|
content:
|
||||||
'application/json':
|
'application/json':
|
||||||
schema:
|
schema:
|
||||||
userID:
|
type: object
|
||||||
type: integer
|
properties:
|
||||||
|
approve:
|
||||||
|
type: boolean
|
||||||
|
required: [ approve ]
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
$ref: '#/components/responses/teamResponse'
|
||||||
|
404:
|
||||||
|
$ref: '#/components/responses/errorResponse'
|
||||||
|
/teams/{teamID}/requests:
|
||||||
|
post:
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: teamID
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
required: true
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: '#/components/responses/teamResponse'
|
$ref: '#/components/responses/teamResponse'
|
||||||
|
@ -147,6 +195,13 @@ components:
|
||||||
enum:
|
enum:
|
||||||
- member
|
- member
|
||||||
- captain
|
- captain
|
||||||
|
userRole:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- notVerified
|
||||||
|
- user
|
||||||
|
- creator
|
||||||
|
- admin
|
||||||
teamMember:
|
teamMember:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -253,10 +308,13 @@ components:
|
||||||
type: string
|
type: string
|
||||||
team:
|
team:
|
||||||
$ref: "#/components/schemas/userTeam"
|
$ref: "#/components/schemas/userTeam"
|
||||||
|
role:
|
||||||
|
$ref: "#/components/schemas/userRole"
|
||||||
required:
|
required:
|
||||||
- id
|
- id
|
||||||
- username
|
- username
|
||||||
- email
|
- email
|
||||||
|
- role
|
||||||
errorResponse:
|
errorResponse:
|
||||||
description: ''
|
description: ''
|
||||||
content:
|
content:
|
||||||
|
|
|
@ -42,6 +42,12 @@ type ServerInterface interface {
|
||||||
// (POST /teams/{teamID}/members)
|
// (POST /teams/{teamID}/members)
|
||||||
PostTeamsTeamIDMembers(ctx echo.Context, teamID int) error
|
PostTeamsTeamIDMembers(ctx echo.Context, teamID int) error
|
||||||
|
|
||||||
|
// (POST /teams/{teamID}/requests)
|
||||||
|
PostTeamsTeamIDRequests(ctx echo.Context, teamID int) error
|
||||||
|
|
||||||
|
// (POST /teams/{teamID}/requests/{userID})
|
||||||
|
PostTeamsTeamIDRequestsUserID(ctx echo.Context, teamID int, userID int) error
|
||||||
|
|
||||||
// (GET /user)
|
// (GET /user)
|
||||||
GetUser(ctx echo.Context) error
|
GetUser(ctx echo.Context) error
|
||||||
|
|
||||||
|
@ -163,6 +169,50 @@ func (w *ServerInterfaceWrapper) PostTeamsTeamIDMembers(ctx echo.Context) error
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PostTeamsTeamIDRequests converts echo context to params.
|
||||||
|
func (w *ServerInterfaceWrapper) PostTeamsTeamIDRequests(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.PostTeamsTeamIDRequests(ctx, teamID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostTeamsTeamIDRequestsUserID converts echo context to params.
|
||||||
|
func (w *ServerInterfaceWrapper) PostTeamsTeamIDRequestsUserID(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))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------- Path parameter "userID" -------------
|
||||||
|
var userID int
|
||||||
|
|
||||||
|
err = runtime.BindStyledParameterWithLocation("simple", false, "userID", runtime.ParamLocationPath, ctx.Param("userID"), &userID)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter userID: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Set(CookieAuthScopes, []string{})
|
||||||
|
|
||||||
|
// Invoke the callback with all the unmarshaled arguments
|
||||||
|
err = w.Handler.PostTeamsTeamIDRequestsUserID(ctx, teamID, userID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// GetUser converts echo context to params.
|
// GetUser converts echo context to params.
|
||||||
func (w *ServerInterfaceWrapper) GetUser(ctx echo.Context) error {
|
func (w *ServerInterfaceWrapper) GetUser(ctx echo.Context) error {
|
||||||
var err error
|
var err error
|
||||||
|
@ -236,6 +286,8 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
||||||
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)
|
||||||
|
router.POST(baseURL+"/teams/:teamID/requests", wrapper.PostTeamsTeamIDRequests)
|
||||||
|
router.POST(baseURL+"/teams/:teamID/requests/:userID", wrapper.PostTeamsTeamIDRequestsUserID)
|
||||||
router.GET(baseURL+"/user", wrapper.GetUser)
|
router.GET(baseURL+"/user", wrapper.GetUser)
|
||||||
router.POST(baseURL+"/user/login", wrapper.PostUserLogin)
|
router.POST(baseURL+"/user/login", wrapper.PostUserLogin)
|
||||||
router.POST(baseURL+"/user/logout", wrapper.PostUserLogout)
|
router.POST(baseURL+"/user/logout", wrapper.PostUserLogout)
|
||||||
|
@ -246,22 +298,24 @@ 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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",
|
||||||
|
"tlEaAAA=",
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSwagger returns the content of the embedded swagger specification file
|
// GetSwagger returns the content of the embedded swagger specification file
|
||||||
|
|
30
api/types.go
30
api/types.go
|
@ -7,6 +7,14 @@ const (
|
||||||
CookieAuthScopes = "cookieAuth.Scopes"
|
CookieAuthScopes = "cookieAuth.Scopes"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Defines values for UserRole.
|
||||||
|
const (
|
||||||
|
Admin UserRole = "admin"
|
||||||
|
Creator UserRole = "creator"
|
||||||
|
NotVerified UserRole = "notVerified"
|
||||||
|
User UserRole = "user"
|
||||||
|
)
|
||||||
|
|
||||||
// Defines values for UserTeamRole.
|
// Defines values for UserTeamRole.
|
||||||
const (
|
const (
|
||||||
Captain UserTeamRole = "captain"
|
Captain UserTeamRole = "captain"
|
||||||
|
@ -44,6 +52,9 @@ type TeamView struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserRole defines model for userRole.
|
||||||
|
type UserRole string
|
||||||
|
|
||||||
// UserTeam defines model for userTeam.
|
// UserTeam defines model for userTeam.
|
||||||
type UserTeam struct {
|
type UserTeam struct {
|
||||||
Id int `json:"id"`
|
Id int `json:"id"`
|
||||||
|
@ -85,6 +96,7 @@ type TeamsListResponse = []TeamView
|
||||||
type UserResponse struct {
|
type UserResponse struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Id int `json:"id"`
|
Id int `json:"id"`
|
||||||
|
Role UserRole `json:"role"`
|
||||||
Team *UserTeam `json:"team,omitempty"`
|
Team *UserTeam `json:"team,omitempty"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
}
|
}
|
||||||
|
@ -108,8 +120,15 @@ type PostTeamsJSONBody struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteTeamsTeamIDMembersJSONBody defines parameters for DeleteTeamsTeamIDMembers.
|
// PostTeamsTeamIDMembersJSONBody defines parameters for PostTeamsTeamIDMembers.
|
||||||
type DeleteTeamsTeamIDMembersJSONBody = interface{}
|
type PostTeamsTeamIDMembersJSONBody struct {
|
||||||
|
Members []int `json:"members"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostTeamsTeamIDRequestsUserIDJSONBody defines parameters for PostTeamsTeamIDRequestsUserID.
|
||||||
|
type PostTeamsTeamIDRequestsUserIDJSONBody struct {
|
||||||
|
Approve bool `json:"approve"`
|
||||||
|
}
|
||||||
|
|
||||||
// PostUserLoginJSONBody defines parameters for PostUserLogin.
|
// PostUserLoginJSONBody defines parameters for PostUserLogin.
|
||||||
type PostUserLoginJSONBody struct {
|
type PostUserLoginJSONBody struct {
|
||||||
|
@ -128,8 +147,11 @@ type PostUserRegisterJSONBody struct {
|
||||||
// PostTeamsJSONRequestBody defines body for PostTeams for application/json ContentType.
|
// PostTeamsJSONRequestBody defines body for PostTeams for application/json ContentType.
|
||||||
type PostTeamsJSONRequestBody PostTeamsJSONBody
|
type PostTeamsJSONRequestBody PostTeamsJSONBody
|
||||||
|
|
||||||
// DeleteTeamsTeamIDMembersJSONRequestBody defines body for DeleteTeamsTeamIDMembers for application/json ContentType.
|
// PostTeamsTeamIDMembersJSONRequestBody defines body for PostTeamsTeamIDMembers for application/json ContentType.
|
||||||
type DeleteTeamsTeamIDMembersJSONRequestBody = DeleteTeamsTeamIDMembersJSONBody
|
type PostTeamsTeamIDMembersJSONRequestBody PostTeamsTeamIDMembersJSONBody
|
||||||
|
|
||||||
|
// PostTeamsTeamIDRequestsUserIDJSONRequestBody defines body for PostTeamsTeamIDRequestsUserID for application/json ContentType.
|
||||||
|
type PostTeamsTeamIDRequestsUserIDJSONRequestBody PostTeamsTeamIDRequestsUserIDJSONBody
|
||||||
|
|
||||||
// PostUserLoginJSONRequestBody defines body for PostUserLogin for application/json ContentType.
|
// PostUserLoginJSONRequestBody defines body for PostUserLogin for application/json ContentType.
|
||||||
type PostUserLoginJSONRequestBody PostUserLoginJSONBody
|
type PostUserLoginJSONRequestBody PostUserLoginJSONBody
|
||||||
|
|
|
@ -2,9 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
"github.com/kelseyhightower/envconfig"
|
"github.com/kelseyhightower/envconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,10 +30,5 @@ func (c *Config) DSN() string {
|
||||||
func GetConfig() (*Config, error) {
|
func GetConfig() (*Config, error) {
|
||||||
c := new(Config)
|
c := new(Config)
|
||||||
|
|
||||||
err := godotenv.Load()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("not .env")
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, envconfig.Process("", c)
|
return c, envconfig.Process("", c)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,12 @@ import { Button, Nav, Container, NavbarBrand, NavbarToggle, Navbar, NavbarCollap
|
||||||
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";
|
||||||
|
import { useRole } from "../utils/roles";
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const params = useLoaderData();
|
const params = useLoaderData();
|
||||||
|
const { hasRole } = useRole();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setUser(params)
|
setUser(params)
|
||||||
}, [params])
|
}, [params])
|
||||||
|
@ -28,28 +31,35 @@ export default () => {
|
||||||
<Nav.Item>
|
<Nav.Item>
|
||||||
<Nav.Link as={Link} className="nav-link" to="/">Игры</Nav.Link>
|
<Nav.Link as={Link} className="nav-link" to="/">Игры</Nav.Link>
|
||||||
</Nav.Item>
|
</Nav.Item>
|
||||||
<Nav.Item>
|
{hasRole("user") ? (<>
|
||||||
<Nav.Link as={Link} className="nav-link" to="/teams">Команды</Nav.Link>
|
<Nav.Item>
|
||||||
</Nav.Item>
|
<Nav.Link as={Link} className="nav-link" to="/teams">Команды</Nav.Link>
|
||||||
|
</Nav.Item>
|
||||||
|
{hasRole("creator") ? (
|
||||||
|
<Nav.Item>
|
||||||
|
<Nav.Link as={Link} className="nav-link" to="/admin">Админка</Nav.Link>
|
||||||
|
</Nav.Item>
|
||||||
|
) : null}
|
||||||
|
</>) : null}
|
||||||
</Nav>
|
</Nav>
|
||||||
<Navbar.Text>
|
<Navbar.Text>
|
||||||
{user ? (
|
{user ? (
|
||||||
<>
|
<>
|
||||||
{user.username}
|
{user.username}
|
||||||
{user.team ? (
|
{user.team ? (
|
||||||
<>(<Link to={`teams/${user.team.id}`}>{user.team.name}</Link>)</>
|
<>(<Link to={`teams/${user.team.id}`}>{user.team.name}</Link>)</>
|
||||||
) : (
|
) : (
|
||||||
<>(без команды)</>
|
<>(без команды)</>
|
||||||
)}
|
)}
|
||||||
<Button type="button" variant="outline-success" onClick={logout}>Выход</Button>
|
<Button type="button" variant="outline-success" onClick={logout}>Выход</Button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<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>
|
</ButtonGroup>
|
||||||
)}
|
)}
|
||||||
</Navbar.Text>
|
</Navbar.Text>
|
||||||
</NavbarCollapse>
|
</NavbarCollapse>
|
||||||
</Container>
|
</Container>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
|
@ -1,22 +1,32 @@
|
||||||
import { useLoaderData, useNavigate, useRouteLoaderData } from "react-router-dom";
|
import { useLoaderData, useNavigate, useRouteLoaderData } from "react-router-dom";
|
||||||
import { Button, Table } from "react-bootstrap";
|
import { Alert, Button, ButtonGroup, Table } from "react-bootstrap";
|
||||||
import { UserProvider } from "../store/user";
|
import { UserProvider } from "../store/user";
|
||||||
import { ajax } from "../utils/fetch";
|
import { ajax } from "../utils/fetch";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
const userRoles = { captain: "Капитан", member: "Участник" };
|
const userRoles = { captain: "Капитан", member: "Участник" };
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const team = useLoaderData();
|
const teamFromRouter = useLoaderData();
|
||||||
|
const [team, setTeam] = useState(teamFromRouter);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
useEffect(() => {
|
||||||
|
setTeam(teamFromRouter);
|
||||||
|
}, [teamFromRouter]);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const {request, setRequest} = useState(false);
|
|
||||||
|
const { user, setUser } = UserProvider.useContainer();
|
||||||
|
|
||||||
|
const [request, setRequest] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setRequest(!!team.requests.find(x => user && x.user.id == user.id));
|
||||||
|
}, [user, team.requests])
|
||||||
|
|
||||||
if (!team) {
|
if (!team) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { user } = UserProvider.useContainer();
|
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -26,14 +36,38 @@ export default () => {
|
||||||
const isCaptain = member && user.team.role == "captain";
|
const isCaptain = member && user.team.role == "captain";
|
||||||
|
|
||||||
const sendRequest = () => {
|
const sendRequest = () => {
|
||||||
|
ajax(`/api/teams/${team.id}/requests`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
}).then(() => setRequest(true)).catch(setError);
|
||||||
|
};
|
||||||
|
const updateMemebers = (members) => {
|
||||||
ajax(`/api/teams/${team.id}/members`, {
|
ajax(`/api/teams/${team.id}/members`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
}).then(() => setRequest(true));
|
body: JSON.stringify({ members }),
|
||||||
};
|
}).then(team => setTeam(team)).catch(setError);
|
||||||
|
};
|
||||||
|
const approveRequest = (userID, approve) => {
|
||||||
|
ajax(`/api/teams/${team.id}/requests/${userID}`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ approve }),
|
||||||
|
}).then(team => setTeam(team)).catch(setError);
|
||||||
|
}
|
||||||
|
const removeMember = (userID) => {
|
||||||
|
const members = team.members.map(x => x.user.id).filter(uid => uid != userID);
|
||||||
|
return updateMemebers(members)
|
||||||
|
}
|
||||||
const leaveTeam = () => {
|
const leaveTeam = () => {
|
||||||
ajax(`/api/teams/${team.id}/members`, {
|
ajax(`/api/teams/${team.id}/members`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
@ -41,8 +75,13 @@ export default () => {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
}).then(() => navigate("/teams"));
|
}).then(team => setTeam(team)).then(() => {
|
||||||
};
|
setUser({
|
||||||
|
...user,
|
||||||
|
team: null,
|
||||||
|
});
|
||||||
|
}).catch(setError);
|
||||||
|
};
|
||||||
const deleteTeam = () => {
|
const deleteTeam = () => {
|
||||||
ajax(`/api/teams/${team.id}`, {
|
ajax(`/api/teams/${team.id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
@ -50,7 +89,13 @@ export default () => {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
}).then(() => navigate("/teams"));
|
}).then(() => {
|
||||||
|
setUser({
|
||||||
|
...user,
|
||||||
|
team: null,
|
||||||
|
});
|
||||||
|
navigate("/teams");
|
||||||
|
}).catch(setError);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
|
@ -58,7 +103,8 @@ export default () => {
|
||||||
<p>Создана: {team.createdAt}</p>
|
<p>Создана: {team.createdAt}</p>
|
||||||
|
|
||||||
{!member && !inOtherTeam && !request ? (<Button onClick={sendRequest}>Отправить заявку в команду</Button>) : null}
|
{!member && !inOtherTeam && !request ? (<Button onClick={sendRequest}>Отправить заявку в команду</Button>) : null}
|
||||||
{request ? (<p>Заявка в команду рассматривается</p>) : null}
|
{request ? (<Alert variant="success">Заявка в команду отправлена</Alert>) : null}
|
||||||
|
{error ? (<Alert variant="danger">{error}</Alert>) : null}
|
||||||
{member && !isCaptain ? (<Button onClick={leaveTeam}>Выйти из команды</Button>) : null}
|
{member && !isCaptain ? (<Button onClick={leaveTeam}>Выйти из команды</Button>) : null}
|
||||||
|
|
||||||
<h2>Участники</h2>
|
<h2>Участники</h2>
|
||||||
|
@ -79,15 +125,51 @@ export default () => {
|
||||||
<td>{tm.createdAt}</td>
|
<td>{tm.createdAt}</td>
|
||||||
<td>
|
<td>
|
||||||
{
|
{
|
||||||
isCaptain && tm.user.id!=user.id
|
isCaptain && tm.user.id != user.id
|
||||||
? (<Button variant="outline-danger">Выгнать</Button>)
|
? (<Button variant="outline-danger" onClick={() => removeMember(tm.user.id)}>Выгнать</Button>)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</Table>
|
</Table>
|
||||||
{isCaptain && (team.members.length == 1)?<Button variant="outline-danger" onClick={deleteTeam}>Удалить команду</Button>:null}
|
{isCaptain
|
||||||
|
? (<>
|
||||||
|
<h2>Заявки</h2>
|
||||||
|
<Table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Имя пользователя</th>
|
||||||
|
<th>Дата заявки</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{team.requests.map(tm => (
|
||||||
|
<tr key={tm.user.id}>
|
||||||
|
<td>{tm.user.username}</td>
|
||||||
|
<td>{tm.createdAt}</td>
|
||||||
|
<td>
|
||||||
|
<ButtonGroup>
|
||||||
|
<Button
|
||||||
|
variant="outline-success"
|
||||||
|
onClick={() => approveRequest(tm.user.id, true)}>
|
||||||
|
Принять
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline-danger"
|
||||||
|
onClick={() => approveRequest(tm.user.id, false)}>
|
||||||
|
Отказать
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
</>)
|
||||||
|
: null}
|
||||||
|
{isCaptain && (team.members.length == 1) ? <Button variant="outline-danger" onClick={deleteTeam}>Удалить команду</Button> : null}
|
||||||
</>);
|
</>);
|
||||||
}
|
}
|
|
@ -2,8 +2,10 @@ import { useState } from "react";
|
||||||
import { Form, Button, Row, Col } from "react-bootstrap";
|
import { Form, Button, Row, Col } from "react-bootstrap";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { ajax } from "../utils/fetch";
|
import { ajax } from "../utils/fetch";
|
||||||
|
import { UserProvider } from "../store/user";
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
|
const {user, setUser} = UserProvider.useContainer();
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -18,7 +20,14 @@ export default () => {
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ name })
|
body: JSON.stringify({ name })
|
||||||
}).then((team) => {
|
}).then((team) => {
|
||||||
navigate(`/teams/${team.id}`)
|
setUser({
|
||||||
|
...user,
|
||||||
|
team: {
|
||||||
|
role: 1,
|
||||||
|
...team,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
navigate(`/teams/${team.id}`);
|
||||||
}).catch(e => setError(e.message))
|
}).catch(e => setError(e.message))
|
||||||
}
|
}
|
||||||
return (<>
|
return (<>
|
||||||
|
|
24
frontend/src/utils/roles.js
Normal file
24
frontend/src/utils/roles.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { UserProvider } from "../store/user"
|
||||||
|
|
||||||
|
const roleHierarchy = {
|
||||||
|
"user": {
|
||||||
|
"user": true
|
||||||
|
},
|
||||||
|
"creator": {
|
||||||
|
"user": true,
|
||||||
|
"creator": true,
|
||||||
|
},
|
||||||
|
"admin": {
|
||||||
|
"user": true,
|
||||||
|
"creator": true,
|
||||||
|
"admin": true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useRole = () => {
|
||||||
|
const { user } = UserProvider.useContainer();
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasRole: (role) => user && !!roleHierarchy[user.role][role]
|
||||||
|
}
|
||||||
|
}
|
7
go.mod
7
go.mod
|
@ -5,7 +5,6 @@ go 1.21.3
|
||||||
require (
|
require (
|
||||||
github.com/dimuska139/go-email-normalizer v1.2.1
|
github.com/dimuska139/go-email-normalizer v1.2.1
|
||||||
github.com/getkin/kin-openapi v0.120.0
|
github.com/getkin/kin-openapi v0.120.0
|
||||||
github.com/joho/godotenv v1.5.1
|
|
||||||
github.com/kelseyhightower/envconfig v1.4.0
|
github.com/kelseyhightower/envconfig v1.4.0
|
||||||
github.com/labstack/echo-contrib v0.15.0
|
github.com/labstack/echo-contrib v0.15.0
|
||||||
github.com/labstack/echo/v4 v4.11.2
|
github.com/labstack/echo/v4 v4.11.2
|
||||||
|
@ -17,6 +16,7 @@ require (
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||||
|
github.com/deepmap/oapi-codegen/v2 v2.0.0 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||||
github.com/go-openapi/swag v0.22.4 // indirect
|
github.com/go-openapi/swag v0.22.4 // indirect
|
||||||
github.com/google/uuid v1.3.1 // indirect
|
github.com/google/uuid v1.3.1 // indirect
|
||||||
|
@ -29,6 +29,9 @@ require (
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||||
|
golang.org/x/mod v0.12.0 // indirect
|
||||||
|
golang.org/x/tools v0.12.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -56,7 +59,7 @@ require (
|
||||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
github.com/wader/gormstore/v2 v2.0.3
|
github.com/wader/gormstore/v2 v2.0.3
|
||||||
golang.org/x/net v0.17.0 // indirect
|
golang.org/x/net v0.17.0 // indirect
|
||||||
golang.org/x/sys v0.13.0 // indirect
|
golang.org/x/sys v0.14.0 // indirect
|
||||||
golang.org/x/text v0.13.0 // indirect
|
golang.org/x/text v0.13.0 // indirect
|
||||||
golang.org/x/time v0.3.0 // indirect
|
golang.org/x/time v0.3.0 // indirect
|
||||||
google.golang.org/protobuf v1.31.0 // indirect
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
|
|
14
go.sum
14
go.sum
|
@ -16,6 +16,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/deepmap/oapi-codegen/v2 v2.0.0 h1:3TS7w3r+XnjKFXcbFbc16pTWzfTy0OLPkCsutEHjWDA=
|
||||||
|
github.com/deepmap/oapi-codegen/v2 v2.0.0/go.mod h1:7zR+ZL3WzLeCkr2k8oWTxEa0v8y/F25ane0l6A5UjLA=
|
||||||
github.com/dimuska139/go-email-normalizer v1.2.1 h1:pJNZnU7uS9MRoYqpoir05B+bCYXrS9sPGE4G1o9EDA8=
|
github.com/dimuska139/go-email-normalizer v1.2.1 h1:pJNZnU7uS9MRoYqpoir05B+bCYXrS9sPGE4G1o9EDA8=
|
||||||
github.com/dimuska139/go-email-normalizer v1.2.1/go.mod h1:fGPWcd/7PSz9aOHusKVYmDk+oKahH/fZTCQ7tTU7e0Y=
|
github.com/dimuska139/go-email-normalizer v1.2.1/go.mod h1:fGPWcd/7PSz9aOHusKVYmDk+oKahH/fZTCQ7tTU7e0Y=
|
||||||
github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg=
|
github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg=
|
||||||
|
@ -104,8 +106,6 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
|
||||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||||
|
@ -235,6 +235,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||||
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
@ -263,8 +265,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
@ -285,6 +287,8 @@ golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtn
|
||||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
|
||||||
|
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
||||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
@ -302,6 +306,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
3
main.go
3
main.go
|
@ -67,7 +67,7 @@ func main() {
|
||||||
|
|
||||||
// --[ Services ]--
|
// --[ Services ]--
|
||||||
userService := service.NewUser(db)
|
userService := service.NewUser(db)
|
||||||
teamService := service.NewTeam(db)
|
teamService := service.NewTeam(db, userService)
|
||||||
gameService := service.NewGame(db)
|
gameService := service.NewGame(db)
|
||||||
|
|
||||||
// --[ HTTP server ]--
|
// --[ HTTP server ]--
|
||||||
|
@ -146,6 +146,7 @@ func main() {
|
||||||
Options: openapi3filter.Options{
|
Options: openapi3filter.Options{
|
||||||
AuthenticationFunc: authFunc,
|
AuthenticationFunc: authFunc,
|
||||||
},
|
},
|
||||||
|
SilenceServersWarning: true,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -45,29 +45,28 @@ func (t *Team) GetTeamsTeamID(ctx echo.Context, teamID int) error {
|
||||||
return t.getTeamResponse(ctx, teamID)
|
return t.getTeamResponse(ctx, teamID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Team) DeleteTeamsTeamIDMembers(ctx echo.Context, teamID int) error {
|
|
||||||
return t.getTeamResponse(ctx, teamID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Team) PostTeamsTeamIDMembers(ctx echo.Context, teamID int) error {
|
func (t *Team) PostTeamsTeamIDMembers(ctx echo.Context, teamID int) error {
|
||||||
team, err := t.TeamService.GetByID(ctx.Request().Context(), uint(teamID))
|
user := contextlib.GetUser(ctx)
|
||||||
if err != nil {
|
if user.Team == nil || user.Team.TeamID != uint(teamID) || user.Team.Role != models.Captain {
|
||||||
return ctx.JSON(http.StatusNotFound, api.ErrorResponse{
|
return ctx.JSON(http.StatusForbidden, api.ErrorResponse{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusForbidden,
|
||||||
|
Message: "Вам нельзя менять состав команды",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &api.PostTeamsTeamIDMembersJSONRequestBody{}
|
||||||
|
if err := ctx.Bind(req); err != nil {
|
||||||
|
return ctx.JSON(http.StatusBadRequest, api.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
user := contextlib.GetUser(ctx)
|
if err := t.TeamService.UpdateMembers(ctx.Request().Context(), uint(teamID), req.Members); err != nil {
|
||||||
if user.Team != nil {
|
return ctx.JSON(http.StatusBadRequest, api.ErrorResponse{
|
||||||
if err := t.TeamService.RemoveMember(ctx.Request().Context(), user.Team.Team, user); err != nil {
|
Code: http.StatusBadRequest,
|
||||||
return err
|
Message: err.Error(),
|
||||||
}
|
})
|
||||||
user.Team = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := t.TeamService.Request(ctx.Request().Context(), team, user); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return t.getTeamResponse(ctx, teamID)
|
return t.getTeamResponse(ctx, teamID)
|
||||||
|
@ -124,7 +123,7 @@ func (t *Team) getTeamResponse(ctx echo.Context, teamID int) error {
|
||||||
members := make([]api.TeamMember, 0, len(team.Members))
|
members := make([]api.TeamMember, 0, len(team.Members))
|
||||||
for _, tm := range team.Members {
|
for _, tm := range team.Members {
|
||||||
members = append(members, api.TeamMember{
|
members = append(members, api.TeamMember{
|
||||||
Role: api.MapRole[tm.Role],
|
Role: api.MapTeamRole[tm.Role],
|
||||||
User: api.UserView{
|
User: api.UserView{
|
||||||
Id: int(tm.User.ID),
|
Id: int(tm.User.ID),
|
||||||
Username: tm.User.Username,
|
Username: tm.User.Username,
|
||||||
|
@ -151,3 +150,62 @@ func (t *Team) getTeamResponse(ctx echo.Context, teamID int) error {
|
||||||
CreatedAt: team.CreatedAt.Format("02.01.06"),
|
CreatedAt: team.CreatedAt.Format("02.01.06"),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// (POST /teams/{teamID}/requests)
|
||||||
|
func (t *Team) PostTeamsTeamIDRequests(ctx echo.Context, teamID int) error {
|
||||||
|
user := contextlib.GetUser(ctx)
|
||||||
|
if user.Team != nil {
|
||||||
|
return ctx.JSON(http.StatusForbidden, api.ErrorResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
Message: "Вы уже в другой команде",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := t.TeamService.Request(ctx.Request().Context(), uint(teamID), user); err != nil {
|
||||||
|
return ctx.JSON(http.StatusBadRequest, api.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.getTeamResponse(ctx, teamID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// (POST /teams/{teamID}/requests/{userID})
|
||||||
|
func (t *Team) PostTeamsTeamIDRequestsUserID(ctx echo.Context, teamID int, userID 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: "Вам нельзя менять состав команды",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
req := &api.PostTeamsTeamIDRequestsUserIDJSONRequestBody{}
|
||||||
|
if err := ctx.Bind(req); err != nil {
|
||||||
|
return ctx.JSON(http.StatusBadRequest, api.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.TeamService.ApproveRequest(ctx.Request().Context(), teamID, uint(userID), req.Approve); err != nil {
|
||||||
|
return ctx.JSON(http.StatusBadRequest, api.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.getTeamResponse(ctx, teamID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// (DELETE /teams/{teamID}/members)
|
||||||
|
func (t *Team) DeleteTeamsTeamIDMembers(ctx echo.Context, teamID int) error {
|
||||||
|
user := contextlib.GetUser(ctx)
|
||||||
|
if err := t.TeamService.DeleteMember(ctx.Request().Context(), teamID, user.ID); err != nil {
|
||||||
|
return ctx.JSON(http.StatusBadRequest, api.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.getTeamResponse(ctx, teamID)
|
||||||
|
}
|
||||||
|
|
|
@ -107,6 +107,7 @@ func (u *User) GetUser(c echo.Context) error {
|
||||||
Username: user.Username,
|
Username: user.Username,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
Team: api.MapUserTeam(user.Team),
|
Team: api.MapUserTeam(user.Team),
|
||||||
|
Role: api.MapUserRole[user.Role],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"slices"
|
"slices"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gitrepo.ru/neonxp/nquest/pkg/models"
|
"gitrepo.ru/neonxp/nquest/pkg/models"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
@ -15,16 +16,40 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Team struct {
|
type Team struct {
|
||||||
DB *gorm.DB
|
DB *gorm.DB
|
||||||
|
User *User
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTeam returns new Team.
|
// NewTeam returns new Team.
|
||||||
func NewTeam(db *gorm.DB) *Team {
|
func NewTeam(db *gorm.DB, user *User) *Team {
|
||||||
return &Team{
|
return &Team{
|
||||||
DB: db,
|
DB: db,
|
||||||
|
User: user,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ts *Team) GetByID(ctx context.Context, id uint) (*models.Team, error) {
|
||||||
|
t := new(models.Team)
|
||||||
|
|
||||||
|
err := ts.DB.
|
||||||
|
WithContext(ctx).
|
||||||
|
Preload("Members").
|
||||||
|
Preload("Members.User").
|
||||||
|
Preload("Requests").
|
||||||
|
Preload("Requests.User").
|
||||||
|
First(t, id).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, ErrTeamNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ts *Team) List(ctx context.Context) ([]*models.Team, error) {
|
func (ts *Team) List(ctx context.Context) ([]*models.Team, error) {
|
||||||
teams := []*models.Team{}
|
teams := []*models.Team{}
|
||||||
|
|
||||||
|
@ -49,28 +74,6 @@ func (ts *Team) Create(ctx context.Context, name string, user *models.User) (*mo
|
||||||
return t, db.Create(t).Error
|
return t, db.Create(t).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ts *Team) GetByID(ctx context.Context, id uint) (*models.Team, error) {
|
|
||||||
t := new(models.Team)
|
|
||||||
|
|
||||||
err := ts.DB.
|
|
||||||
WithContext(ctx).
|
|
||||||
Preload("Members").
|
|
||||||
Preload("Members.User").
|
|
||||||
Preload("Requests").
|
|
||||||
Preload("Requests.User").
|
|
||||||
First(t, id).
|
|
||||||
Error
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, ErrTeamNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *Team) Delete(ctx context.Context, id uint) error {
|
func (ts *Team) Delete(ctx context.Context, id uint) error {
|
||||||
if err := ts.DB.WithContext(ctx).Delete(&models.Team{}, id).Error; err != nil {
|
if err := ts.DB.WithContext(ctx).Delete(&models.Team{}, id).Error; err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
@ -83,7 +86,12 @@ func (ts *Team) Delete(ctx context.Context, id uint) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ts *Team) Request(ctx context.Context, team *models.Team, user *models.User) error {
|
func (ts *Team) Request(ctx context.Context, teamID uint, user *models.User) error {
|
||||||
|
team, err := ts.GetByID(ctx, teamID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return ts.DB.
|
return ts.DB.
|
||||||
WithContext(ctx).
|
WithContext(ctx).
|
||||||
Clauses(clause.OnConflict{DoNothing: true}).
|
Clauses(clause.OnConflict{DoNothing: true}).
|
||||||
|
@ -94,37 +102,108 @@ func (ts *Team) Request(ctx context.Context, team *models.Team, user *models.Use
|
||||||
Error
|
Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ts *Team) ApproveMember(ctx context.Context, team *models.Team, user *models.User) error {
|
func (ts *Team) UpdateMembers(
|
||||||
team.Requests = slices.DeleteFunc(team.Requests, func(tr *models.TeamRequest) bool {
|
ctx context.Context,
|
||||||
return tr.UserID == user.ID
|
teamID uint,
|
||||||
})
|
newMembers []int,
|
||||||
|
) error {
|
||||||
team.Members = append(team.Members, &models.TeamMember{
|
team, err := ts.GetByID(ctx, teamID)
|
||||||
Team: team,
|
if err != nil {
|
||||||
User: user,
|
|
||||||
Role: models.Member,
|
|
||||||
})
|
|
||||||
|
|
||||||
db := ts.DB.WithContext(ctx)
|
|
||||||
if err := db.Delete(&models.TeamRequest{}, `user_id = ?`, user.ID).Error; err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.Save(team).Error
|
newMembersList := make([]*models.TeamMember, 0, len(newMembers))
|
||||||
|
|
||||||
|
for _, tm := range team.Members {
|
||||||
|
idx, ok := slices.BinarySearch(newMembers, int(tm.UserID))
|
||||||
|
if ok {
|
||||||
|
newMembers = slices.Delete(newMembers, idx, idx)
|
||||||
|
newMembersList = append(newMembersList, tm)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := ts.DB.WithContext(ctx).Delete(tm).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, userID := range newMembers {
|
||||||
|
_, found := slices.BinarySearchFunc(team.Members, userID, func(tm *models.TeamMember, i int) int {
|
||||||
|
return int(tm.UserID) - i
|
||||||
|
})
|
||||||
|
if found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
user, err := ts.User.GetUserByID(ctx, uint(userID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newMembersList = append(newMembersList, &models.TeamMember{
|
||||||
|
TeamID: teamID,
|
||||||
|
Team: team,
|
||||||
|
UserID: user.ID,
|
||||||
|
User: user,
|
||||||
|
Role: models.Member,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
team.Members = newMembersList
|
||||||
|
|
||||||
|
return ts.DB.
|
||||||
|
WithContext(ctx).
|
||||||
|
Session(&gorm.Session{FullSaveAssociations: true}).
|
||||||
|
Updates(team).
|
||||||
|
Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ts *Team) DeclineMember(ctx context.Context, team *models.Team, user *models.User) error {
|
func (ts *Team) ApproveRequest(ctx context.Context, teamID int, userID uint, approve bool) error {
|
||||||
team.Requests = slices.DeleteFunc(team.Requests, func(tr *models.TeamRequest) bool {
|
team, err := ts.GetByID(ctx, uint(teamID))
|
||||||
return tr.UserID == user.ID
|
if err != nil {
|
||||||
})
|
|
||||||
db := ts.DB.WithContext(ctx)
|
|
||||||
if err := db.Delete(&models.TeamRequest{}, `user_id = ?`, user.ID).Error; err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(team).Error
|
idx, found := slices.BinarySearchFunc(team.Requests, userID, func(tr *models.TeamRequest, i uint) int {
|
||||||
|
return int(tr.UserID - i)
|
||||||
|
})
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
request := team.Requests[idx]
|
||||||
|
team.Requests = slices.DeleteFunc(team.Requests, func(tr *models.TeamRequest) bool {
|
||||||
|
return tr.UserID == uint(userID)
|
||||||
|
})
|
||||||
|
if err := ts.DB.WithContext(ctx).Delete(request).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if approve {
|
||||||
|
team.Members = append(team.Members, &models.TeamMember{
|
||||||
|
Team: team,
|
||||||
|
TeamID: uint(teamID),
|
||||||
|
UserID: uint(userID),
|
||||||
|
Role: models.Member,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return ts.DB.WithContext(ctx).Save(team).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ts *Team) RemoveMember(ctx context.Context, team *models.Team, user *models.User) error {
|
func (ts *Team) DeleteMember(ctx context.Context, teamID int, userID uint) error {
|
||||||
return ts.DB.WithContext(ctx).Delete(&models.TeamMember{}, `user_id = ? and team_id = ?`, user.ID, team.ID).Error
|
team, err := ts.GetByID(ctx, uint(teamID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
idx, found := slices.BinarySearchFunc(team.Members, userID, func(tm *models.TeamMember, u uint) int {
|
||||||
|
return int(tm.UserID - u)
|
||||||
|
})
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
member := team.Members[idx]
|
||||||
|
|
||||||
|
return ts.DB.WithContext(ctx).Delete(member).Error
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue