generated from VLADIMIR/template
add base
This commit is contained in:
Binary file not shown.
@@ -19,6 +19,7 @@ import (
|
||||
"evening_detective_server/internal/repos/games_repo"
|
||||
"evening_detective_server/internal/repos/refresh_tokens_repo"
|
||||
"evening_detective_server/internal/repos/scenarios_repo"
|
||||
"evening_detective_server/internal/repos/screen_sessions_repo"
|
||||
"evening_detective_server/internal/repos/team_answers_repo"
|
||||
"evening_detective_server/internal/repos/teams_repo"
|
||||
"evening_detective_server/internal/repos/users_repo"
|
||||
@@ -128,6 +129,7 @@ func main() {
|
||||
actionsRepo := actions_repo.NewActionsRepo(dbpool)
|
||||
applicationsRepo := applications_repo.NewApplicationsRepo(dbpool)
|
||||
answersRepo := team_answers_repo.NewTeamAnswersRepo(dbpool)
|
||||
screenRepo := screen_sessions_repo.NewScreenSessionsRepo(dbpool)
|
||||
gameService := game_service.NewGameService(
|
||||
gameRepo,
|
||||
teamRepo,
|
||||
@@ -172,6 +174,8 @@ func main() {
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/GetTerms": true,
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/GetPrivacy": true,
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/GetConsent": true,
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/CreateScreenSession": true,
|
||||
"/crabs.evening_detective_server.EveningDetectiveServer/GetScreenState": true,
|
||||
},
|
||||
processorJWT,
|
||||
),
|
||||
@@ -186,6 +190,7 @@ func main() {
|
||||
fileService,
|
||||
scenarioService,
|
||||
gameService,
|
||||
screenRepo,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
+103
-8
@@ -2,9 +2,12 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"evening_detective_server/docs"
|
||||
@@ -14,6 +17,7 @@ import (
|
||||
"evening_detective_server/internal/modules/scenario_archive"
|
||||
"evening_detective_server/internal/modules/string_tools"
|
||||
"evening_detective_server/internal/repos/scenarios_repo"
|
||||
"evening_detective_server/internal/repos/screen_sessions_repo"
|
||||
"evening_detective_server/internal/repos/users_repo"
|
||||
"evening_detective_server/internal/services/file_service"
|
||||
"evening_detective_server/internal/services/game_service"
|
||||
@@ -22,6 +26,7 @@ import (
|
||||
"evening_detective_server/internal/services/users_service"
|
||||
proto "evening_detective_server/proto"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"google.golang.org/genproto/googleapis/api/httpbody"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -40,6 +45,7 @@ type server struct {
|
||||
fileService *file_service.FileService
|
||||
scenarioService *scenarios_service.ScenarioService
|
||||
gameService *game_service.GameService
|
||||
screenRepo *screen_sessions_repo.ScreenSessionsRepo
|
||||
}
|
||||
|
||||
func NewServer(
|
||||
@@ -48,6 +54,7 @@ func NewServer(
|
||||
fileService *file_service.FileService,
|
||||
scenarioService *scenarios_service.ScenarioService,
|
||||
gameService *game_service.GameService,
|
||||
screenRepo *screen_sessions_repo.ScreenSessionsRepo,
|
||||
) proto.EveningDetectiveServerServer {
|
||||
return &server{
|
||||
usersService: usersService,
|
||||
@@ -55,23 +62,111 @@ func NewServer(
|
||||
fileService: fileService,
|
||||
scenarioService: scenarioService,
|
||||
gameService: gameService,
|
||||
screenRepo: screenRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) CreateScreenSession(context.Context, *proto.CreateScreenSessionReq) (*proto.CreateScreenSessionRsp, error) {
|
||||
panic("CreateScreenSession is not implemented")
|
||||
func (s *server) CreateScreenSession(ctx context.Context, _ *proto.CreateScreenSessionReq) (*proto.CreateScreenSessionRsp, error) {
|
||||
token := uuid.New().String()
|
||||
id, err := s.screenRepo.Create(ctx, hashScreenToken(token))
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "не удалось создать сессию экрана")
|
||||
}
|
||||
return &proto.CreateScreenSessionRsp{ScreenId: fmt.Sprint(id), AccessToken: token}, nil
|
||||
}
|
||||
|
||||
func (s *server) GetScreenState(context.Context, *proto.GetScreenStateReq) (*proto.GetScreenStateRsp, error) {
|
||||
panic("GetScreenState is not implemented")
|
||||
func (s *server) GetScreenState(ctx context.Context, req *proto.GetScreenStateReq) (*proto.GetScreenStateRsp, error) {
|
||||
if req == nil || req.ScreenId == "" || req.AccessToken == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "некорректные данные экрана")
|
||||
}
|
||||
id, err := strconv.Atoi(req.ScreenId)
|
||||
if err != nil || id <= 0 {
|
||||
return nil, status.Error(codes.InvalidArgument, "некорректный идентификатор экрана")
|
||||
}
|
||||
session, err := s.screenRepo.Get(ctx, id, hashScreenToken(req.AccessToken))
|
||||
if errors.Is(err, screen_sessions_repo.ErrScreenNotFound) {
|
||||
return nil, status.Error(codes.Unauthenticated, "недействительная сессия экрана")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "не удалось получить состояние экрана")
|
||||
}
|
||||
return &proto.GetScreenStateRsp{State: screenState(session)}, nil
|
||||
}
|
||||
|
||||
func (s *server) BindScreen(context.Context, *proto.BindScreenReq) (*proto.BindScreenRsp, error) {
|
||||
panic("BindScreen is not implemented")
|
||||
func (s *server) BindScreen(ctx context.Context, req *proto.BindScreenReq) (*proto.BindScreenRsp, error) {
|
||||
if !canManageScreens(ctx) {
|
||||
return nil, status.Error(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
if req == nil || req.ScreenId == "" || req.AccessToken == "" || req.GameId <= 0 {
|
||||
return nil, status.Error(codes.InvalidArgument, "некорректные данные привязки")
|
||||
}
|
||||
id, err := strconv.Atoi(req.ScreenId)
|
||||
if err != nil || id <= 0 {
|
||||
return nil, status.Error(codes.InvalidArgument, "некорректный идентификатор экрана")
|
||||
}
|
||||
if _, err = s.gameService.GetGame(ctx, int(req.GameId)); err != nil {
|
||||
return nil, status.Error(codes.NotFound, "игра не найдена")
|
||||
}
|
||||
hash := hashScreenToken(req.AccessToken)
|
||||
if err = s.screenRepo.Bind(ctx, id, int(req.GameId), hash); err != nil {
|
||||
if errors.Is(err, screen_sessions_repo.ErrScreenNotFound) {
|
||||
return nil, status.Error(codes.Unauthenticated, "недействительная сессия экрана")
|
||||
}
|
||||
return nil, status.Error(codes.Internal, "не удалось привязать экран")
|
||||
}
|
||||
session, err := s.screenRepo.Get(ctx, id, hash)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "не удалось получить состояние экрана")
|
||||
}
|
||||
return &proto.BindScreenRsp{State: screenState(session)}, nil
|
||||
}
|
||||
|
||||
func (s *server) UnbindScreen(context.Context, *proto.UnbindScreenReq) (*proto.UnbindScreenRsp, error) {
|
||||
panic("UnbindScreen is not implemented")
|
||||
func (s *server) UnbindScreen(ctx context.Context, req *proto.UnbindScreenReq) (*proto.UnbindScreenRsp, error) {
|
||||
if !canManageScreens(ctx) {
|
||||
return nil, status.Error(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
if req == nil || req.ScreenId == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "некорректный идентификатор экрана")
|
||||
}
|
||||
id, err := strconv.Atoi(req.ScreenId)
|
||||
if err != nil || id <= 0 {
|
||||
return nil, status.Error(codes.InvalidArgument, "некорректный идентификатор экрана")
|
||||
}
|
||||
if err = s.screenRepo.Unbind(ctx, id); err != nil {
|
||||
if errors.Is(err, screen_sessions_repo.ErrScreenNotFound) {
|
||||
return nil, status.Error(codes.NotFound, "экран не найден")
|
||||
}
|
||||
return nil, status.Error(codes.Internal, "не удалось отвязать экран")
|
||||
}
|
||||
return &proto.UnbindScreenRsp{}, nil
|
||||
}
|
||||
|
||||
func canManageScreens(ctx context.Context) bool {
|
||||
claims, ok := ctx.Value("claims").(*processor_jwt.JWTClaims)
|
||||
if !ok || claims == nil {
|
||||
return false
|
||||
}
|
||||
return roles.HasRole(claims, roles.Organizer) || roles.HasRole(claims, roles.Author)
|
||||
}
|
||||
|
||||
func hashScreenToken(token string) string {
|
||||
sum := sha256.Sum256([]byte(token))
|
||||
return base64.RawURLEncoding.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func screenState(session *screen_sessions_repo.ScreenSession) *proto.ScreenState {
|
||||
state := &proto.ScreenState{
|
||||
Id: fmt.Sprint(session.ID),
|
||||
Status: "waiting",
|
||||
Mode: proto.ScreenMode_SCREEN_MODE_UNSPECIFIED,
|
||||
}
|
||||
if session.GameID != nil {
|
||||
gameID := int32(*session.GameID)
|
||||
state.GameId = &gameID
|
||||
state.Status = "bound"
|
||||
state.Mode = proto.ScreenMode_SCREEN_MODE_COVER
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
func (s *server) SetScreenMode(context.Context, *proto.SetScreenModeReq) (*proto.SetScreenModeRsp, error) {
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package screen_sessions_repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
var ErrScreenNotFound = errors.New("Экран не найден")
|
||||
|
||||
type ScreenSession struct {
|
||||
ID int
|
||||
GameID *int
|
||||
}
|
||||
|
||||
type ScreenSessionsRepo struct {
|
||||
pool *pgxpool.Pool
|
||||
}
|
||||
|
||||
func NewScreenSessionsRepo(pool *pgxpool.Pool) *ScreenSessionsRepo {
|
||||
return &ScreenSessionsRepo{pool: pool}
|
||||
}
|
||||
|
||||
func (r *ScreenSessionsRepo) Create(ctx context.Context, tokenHash string) (int, error) {
|
||||
var id int
|
||||
err := r.pool.QueryRow(ctx, `
|
||||
INSERT INTO screen_sessions (access_token_hash)
|
||||
VALUES ($1)
|
||||
RETURNING id`, tokenHash).Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (r *ScreenSessionsRepo) Get(ctx context.Context, id int, tokenHash string) (*ScreenSession, error) {
|
||||
session := &ScreenSession{ID: id}
|
||||
err := r.pool.QueryRow(ctx, `
|
||||
SELECT game_id
|
||||
FROM screen_sessions
|
||||
WHERE id = $1 AND access_token_hash = $2`, id, tokenHash).
|
||||
Scan(&session.GameID)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, ErrScreenNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (r *ScreenSessionsRepo) Bind(ctx context.Context, id, gameID int, tokenHash string) error {
|
||||
result, err := r.pool.Exec(ctx, `
|
||||
UPDATE screen_sessions
|
||||
SET game_id = $1, updated_at = NOW()
|
||||
WHERE id = $2 AND access_token_hash = $3`, gameID, id, tokenHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.RowsAffected() == 0 {
|
||||
return ErrScreenNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ScreenSessionsRepo) Unbind(ctx context.Context, id int) error {
|
||||
result, err := r.pool.Exec(ctx, `
|
||||
UPDATE screen_sessions
|
||||
SET game_id = NULL, updated_at = NOW()
|
||||
WHERE id = $1`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.RowsAffected() == 0 {
|
||||
return ErrScreenNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
-- +goose Up
|
||||
CREATE TABLE IF NOT EXISTS screen_sessions (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
access_token_hash TEXT NOT NULL UNIQUE,
|
||||
game_id INTEGER REFERENCES games (id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
DROP TABLE IF EXISTS screen_sessions;
|
||||
Reference in New Issue
Block a user