Compare commits
128 Commits
0e399d92eb
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| af7ad13ca4 | |||
| 3b6d82077f | |||
| 6f4c5a3aa7 | |||
| 544fac2a76 | |||
| 813493ffa7 | |||
| 235faddaa8 | |||
| bed126bd85 | |||
| 26f5760987 | |||
| 0c987d39b3 | |||
| ae63de364f | |||
| 2f5dd75460 | |||
| 7f88fc6b0f | |||
| b8b6d86393 | |||
| ae20c57ecb | |||
| f23a0c2152 | |||
| f1c69c0f1f | |||
| 8c2dd57e08 | |||
| a107faa366 | |||
| b173f6d5c4 | |||
| f76a23ffc9 | |||
| 40c36bb784 | |||
| 1a51f10c54 | |||
| 96346bd63b | |||
| 129f0fdfbd | |||
| 6515c45a1c | |||
| a1b83ff514 | |||
| 27ac629910 | |||
| f1784fad7e | |||
| 8864043012 | |||
| b54e9e714e | |||
| 2470fc071b | |||
| 3fd528fbbd | |||
| aba3fb0eb1 | |||
| 825d330056 | |||
| 3a30123096 | |||
| ae82f8e0d9 | |||
| 4ae2715ab0 | |||
| 05cab1df89 | |||
| 2e8afd3948 | |||
| edd3db35ae | |||
| 856f12f2e3 | |||
| 6ad47cbc38 | |||
| 83868cc778 | |||
| 795edad998 | |||
| 3612805009 | |||
| 186d09ba5a | |||
| 645f6a7246 | |||
| 9e0a19d25a | |||
| c3e5654ab4 | |||
| 1c63cc1747 | |||
| ccc4f126f6 | |||
| 2bc2bf45c7 | |||
| 5ab7ae0fcd | |||
| caaed14ebc | |||
| 6cbf29031c | |||
| a044093747 | |||
| 4280d5376a | |||
| 3b9c77b422 | |||
| 5604732fcb | |||
| 1964f4241e | |||
| cf47f1979f | |||
| 18acd58ff3 | |||
| 80b7877a09 | |||
| 1d157f284d | |||
| ead2657a22 | |||
| a37b70b92a | |||
| 6b18709e61 | |||
| 3b182d7380 | |||
| 48e7adace0 | |||
| 468256b908 | |||
| ad248e3041 | |||
| 8e45531b8d | |||
| 383e12b718 | |||
| 49c415d4b9 | |||
| 2192bf4e77 | |||
| ee76743097 | |||
| 09b04de9c3 | |||
| d372104760 | |||
| e1a6be0836 | |||
| 0fe8b77d12 | |||
| dad8d1c3a2 | |||
| 93e91ea6c0 | |||
| c144123cff | |||
| 9b7241031c | |||
| 0657f36206 | |||
| 9f484366bb | |||
| 7585e84fad | |||
| 6707967fb5 | |||
| 88de56c140 | |||
| d56f7ec990 | |||
| fc944329a5 | |||
| 52108d17bb | |||
| 9cc6646fe3 | |||
| be8e7fa482 | |||
| b17599eb39 | |||
| 36aaa49273 | |||
| b6e3fb8596 | |||
| 54706f0aba | |||
| 3a2980e9d0 | |||
| 77854681a0 | |||
| b852bebd63 | |||
| 2fa0be8bcb | |||
| c5c9f22f69 | |||
| ba0f0caecf | |||
| 2e510b22a4 | |||
| e7668754fd | |||
| 94833338f9 | |||
| 3c1514a144 | |||
| 6b60c4e533 | |||
| 320273738d | |||
| 06f1128ad0 | |||
| 42dc516e29 | |||
| 49999e9e70 | |||
| 63d1e85c7f | |||
| d380fa1f46 | |||
| 153b50d3a2 | |||
| e38b7ebb88 | |||
| 7de3a6324f | |||
| e0d39bbd72 | |||
| 9b921fe1fa | |||
| e7dfcd8b4d | |||
| 4b44bad1c0 | |||
| e409a69a54 | |||
| 107504317e | |||
| 73838d7773 | |||
| e1b342d12c | |||
| 4bd18ee756 | |||
| 8643af86ee |
@@ -17,9 +17,16 @@
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
bin/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
||||
.idea
|
||||
go.sum
|
||||
|
||||
store.db
|
||||
|
||||
cmd/text_to_story/*.txt
|
||||
cmd/text_to_story/*.json
|
||||
data/
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceFolder}/cmd/evening_detective",
|
||||
"args": [
|
||||
"--local"
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"buildFlags": "-tags local"
|
||||
},
|
||||
{
|
||||
"name": "Launch for tests",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceFolder}/cmd/evening_detective",
|
||||
"args": [
|
||||
"--local"
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"buildFlags": "-tags local",
|
||||
"env": {
|
||||
"STORY_FILENAME": "./internal/tests/story.json",
|
||||
"DB_FILENAME": "./internal/tests/store.db"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,58 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"gwmux"
|
||||
"ввода",
|
||||
"внимательно",
|
||||
"вопрос",
|
||||
"второй",
|
||||
"дает",
|
||||
"Дело",
|
||||
"Детективы",
|
||||
"диалога",
|
||||
"диалогом",
|
||||
"другой",
|
||||
"задать",
|
||||
"запущен",
|
||||
"корректный",
|
||||
"Можно",
|
||||
"Название",
|
||||
"найдена",
|
||||
"найдено",
|
||||
"Нельзя",
|
||||
"Открываем",
|
||||
"Открытие",
|
||||
"открытую",
|
||||
"открыть",
|
||||
"получение",
|
||||
"после",
|
||||
"правила",
|
||||
"Приложение",
|
||||
"приложением",
|
||||
"проходами",
|
||||
"проходом",
|
||||
"сервер",
|
||||
"скрытой",
|
||||
"скрытую",
|
||||
"существует",
|
||||
"сходить",
|
||||
"Такой",
|
||||
"Текст",
|
||||
"Точка",
|
||||
"точки",
|
||||
"точку",
|
||||
"читайте",
|
||||
"AUTOINCREMENT",
|
||||
"GOARCH",
|
||||
"gopdf",
|
||||
"gwmux",
|
||||
"localtime",
|
||||
"palces",
|
||||
"protoc",
|
||||
"qrcode",
|
||||
"signintech",
|
||||
"stretchr"
|
||||
],
|
||||
"makefile.configureOnOpen": false,
|
||||
"go.testFlags": [
|
||||
"-count=1",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Gen Config",
|
||||
"type": "shell",
|
||||
"isBackground": true,
|
||||
"command": "make generate",
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$go"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Tests",
|
||||
"type": "shell",
|
||||
"isBackground": true,
|
||||
"command": "make test",
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$go"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
FROM alpine:latest
|
||||
RUN apk add --no-cache ca-certificates
|
||||
COPY bin/evening_detective /usr/local/bin/evening_detective
|
||||
COPY evening_detective_linux_arm64 /usr/local/bin/evening_detective
|
||||
RUN chmod +x /usr/local/bin/evening_detective
|
||||
CMD ["/usr/local/bin/evening_detective"]
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
FROM golang:1.26-alpine
|
||||
RUN apk add --no-cache gcc musl-dev
|
||||
@@ -12,5 +12,34 @@ generate:
|
||||
run:
|
||||
go run ./cmd/evening_detective/main.go
|
||||
|
||||
build:
|
||||
go build -o bin/evening_detective cmd/evening_detective/main.go
|
||||
build-builder:
|
||||
docker build -f Dockerfile.builder -t my-go-builder .
|
||||
|
||||
build-macos:
|
||||
rm -rf bin
|
||||
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -o bin/evening_detective_macos_arm64 cmd/evening_detective/main.go
|
||||
cp bin/evening_detective_macos_arm64 ../evening_detective_stories
|
||||
ls ../evening_detective_stories | grep Дело | xargs -I {} cp -f bin/evening_detective_macos_arm64 "../evening_detective_stories/{}/game/"
|
||||
|
||||
build-linux:
|
||||
docker run --rm \
|
||||
-v "$$PWD":/app \
|
||||
-w /app \
|
||||
my-go-builder sh -c \
|
||||
"CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -o bin/evening_detective_linux_arm64 cmd/evening_detective/main.go"
|
||||
ls ../evening_detective_stories | grep Дело | xargs -I {} cp -f bin/evening_detective_linux_arm64 "../evening_detective_stories/{}/game/"
|
||||
ls ../evening_detective_stories | grep Дело | xargs -I {} cp -f docker-compose.yml "../evening_detective_stories/{}/game/"
|
||||
ls ../evening_detective_stories | grep Дело | xargs -I {} cp -f Dockerfile "../evening_detective_stories/{}/game/"
|
||||
|
||||
text_to_story:
|
||||
rm -f ./cmd/text_to_story/story.json
|
||||
go run ./cmd/text_to_story/main.go
|
||||
|
||||
clear:
|
||||
rm ./internal/tests/store.db
|
||||
|
||||
test:
|
||||
go test -count=1 ./...
|
||||
|
||||
text_to_program: text_to_story
|
||||
cp ./cmd/text_to_story/story.json ./data/story/story.json
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# template
|
||||
# evening_detective
|
||||
|
||||
Шалон для Go сервисов (имя репо должно быть snake case)
|
||||
Сервис игры "Вечерний детектив"
|
||||
|
||||
Init
|
||||
|
||||
@@ -14,3 +14,9 @@ go mod tidy
|
||||
```shell
|
||||
make run
|
||||
```
|
||||
|
||||
Сборка
|
||||
|
||||
```shell
|
||||
make build
|
||||
```
|
||||
|
||||
@@ -34,19 +34,20 @@ service EveningDetective {
|
||||
|
||||
rpc GetTeam(GetTeamReq) returns (GetTeamRsp) {
|
||||
option (google.api.http) = {
|
||||
get: "/teams/{id}"
|
||||
};
|
||||
}
|
||||
|
||||
rpc DeleteTeams(DeleteTeamsReq) returns (DeleteTeamsRsp) {
|
||||
option (google.api.http) = {
|
||||
delete: "/teams"
|
||||
get: "/team"
|
||||
};
|
||||
}
|
||||
|
||||
rpc AddAction(AddActionReq) returns (AddActionRsp) {
|
||||
option (google.api.http) = {
|
||||
post: "/actions"
|
||||
post: "/team/actions",
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
rpc GetGame(GetGameReq) returns (GetGameRsp) {
|
||||
option (google.api.http) = {
|
||||
get: "/game"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -70,6 +71,25 @@ service EveningDetective {
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
rpc DownloadTeamsQrCodesFile(DownloadTeamsQrCodesFileReq) returns (DownloadTeamsQrCodesFileRsp) {
|
||||
option (google.api.http) = {
|
||||
get: "/teams/pdf"
|
||||
};
|
||||
}
|
||||
|
||||
rpc GetGraph(GetGraphReq) returns (GetGraphRsp) {
|
||||
option (google.api.http) = {
|
||||
get: "/graph"
|
||||
};
|
||||
}
|
||||
|
||||
rpc UpdateNode(UpdateNodeReq) returns (UpdateNodeRsp) {
|
||||
option (google.api.http) = {
|
||||
put : "/graph/nodes",
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
message PingReq {}
|
||||
@@ -89,8 +109,8 @@ message AddTeamsRsp {
|
||||
}
|
||||
|
||||
message TeamFull {
|
||||
int64 id = 1;
|
||||
string name = 2;
|
||||
int64 id = 1;
|
||||
string name = 2;
|
||||
string password = 3;
|
||||
}
|
||||
|
||||
@@ -107,37 +127,57 @@ message GetTeamsCSVRsp {
|
||||
}
|
||||
|
||||
message TeamAdvanced {
|
||||
int64 id = 1;
|
||||
string name = 2;
|
||||
int64 spendTime = 3;
|
||||
repeated Application applications = 4;
|
||||
int64 id = 1;
|
||||
string name = 2;
|
||||
string password = 3;
|
||||
string url = 4;
|
||||
int64 spendTime = 5;
|
||||
repeated Application applications = 6;
|
||||
}
|
||||
|
||||
message Application {
|
||||
string name = 1;
|
||||
int64 id = 1;
|
||||
string name = 2;
|
||||
string state = 3;
|
||||
string number = 4;
|
||||
}
|
||||
|
||||
message GetTeamReq {
|
||||
int64 id = 1;
|
||||
message Door {
|
||||
string code = 1;
|
||||
string name = 2;
|
||||
bool show = 3;
|
||||
}
|
||||
|
||||
message GetTeamReq {}
|
||||
|
||||
message GetTeamRsp {
|
||||
repeated AddActionRsp actions = 1;
|
||||
string name = 1;
|
||||
repeated Action actions = 2;
|
||||
}
|
||||
|
||||
message DeleteTeamsReq {}
|
||||
|
||||
message DeleteTeamsRsp {}
|
||||
|
||||
message AddActionReq {
|
||||
string to = 1;
|
||||
string place = 1;
|
||||
}
|
||||
|
||||
message AddActionRsp {
|
||||
int64 id = 1;
|
||||
string to = 2;
|
||||
string text = 3;
|
||||
repeated Application applications = 4;
|
||||
message AddActionRsp {}
|
||||
|
||||
message Action {
|
||||
int64 id = 1;
|
||||
string place = 2;
|
||||
string name = 3;
|
||||
string text = 4;
|
||||
string image = 5;
|
||||
repeated Application applications = 6;
|
||||
bool hidden = 7;
|
||||
repeated Door doors = 8;
|
||||
}
|
||||
|
||||
message GetGameReq {}
|
||||
|
||||
message GetGameRsp {
|
||||
string state = 1;
|
||||
string startAt = 2;
|
||||
string endAt = 3;
|
||||
}
|
||||
|
||||
message GameStartReq {}
|
||||
@@ -151,8 +191,57 @@ message GameStopReq {
|
||||
message GameStopRsp {}
|
||||
|
||||
message GiveApplicationsReq {
|
||||
int64 teamId = 1;
|
||||
int64 teamId = 1;
|
||||
repeated Application applications = 2;
|
||||
}
|
||||
|
||||
message GiveApplicationsRsp {}
|
||||
|
||||
message DownloadTeamsQrCodesFileReq {}
|
||||
|
||||
message DownloadTeamsQrCodesFileRsp {
|
||||
bytes result = 1;
|
||||
}
|
||||
|
||||
message GetGraphReq {}
|
||||
|
||||
message GetGraphRsp {
|
||||
repeated GraphNode nodes = 1;
|
||||
repeated Edge edges = 2;
|
||||
int32 countNodes = 3;
|
||||
int32 countEdges = 4;
|
||||
|
||||
message Edge {
|
||||
string from = 1;
|
||||
string to = 2;
|
||||
string arrows = 3;
|
||||
string type = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message UpdateNodeReq {
|
||||
string code = 1;
|
||||
GraphNode node = 2;
|
||||
}
|
||||
|
||||
message UpdateNodeRsp {}
|
||||
|
||||
message GraphNode {
|
||||
string code = 1;
|
||||
string name = 2;
|
||||
string text = 3;
|
||||
string image = 4;
|
||||
repeated GraphApplication applications = 5;
|
||||
bool hidden = 7;
|
||||
repeated GraphDoor doors = 8;
|
||||
}
|
||||
|
||||
message GraphApplication {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message GraphDoor {
|
||||
string code = 1;
|
||||
string name = 2;
|
||||
bool show = 3;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
### Получение игры
|
||||
|
||||
GET http://localhost:8090/game
|
||||
|
||||
### Старт игры
|
||||
|
||||
POST http://localhost:8090/game/start
|
||||
|
||||
### Стоп игры
|
||||
|
||||
POST http://localhost:8090/game/stop
|
||||
|
||||
### Получение списка комад
|
||||
|
||||
GET http://localhost:8090/teams
|
||||
|
||||
### Добавление команд
|
||||
|
||||
POST http://localhost:8090/teams
|
||||
|
||||
{
|
||||
"teams": [
|
||||
{
|
||||
"name": "Облако"
|
||||
},
|
||||
{
|
||||
"name": "Кустик"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
### Получение команды
|
||||
|
||||
GET http://localhost:8090/team
|
||||
X-Id: name
|
||||
X-Password: pass
|
||||
|
||||
|
||||
### Ход команды
|
||||
|
||||
POST http://localhost:8090/team/actions
|
||||
X-Id: 1
|
||||
X-Password: pass
|
||||
|
||||
{
|
||||
"place": "A-1"
|
||||
}
|
||||
|
||||
### Выдача приложения
|
||||
|
||||
POST http://localhost:8090/teams/1/applications
|
||||
|
||||
{
|
||||
"applications": [
|
||||
{
|
||||
"id": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
### Qr коды команд в pdf
|
||||
|
||||
GET http://localhost:8090/teams/pdf
|
||||
@@ -2,34 +2,100 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"evening_detective/internal/app"
|
||||
"evening_detective/internal/config"
|
||||
"evening_detective/internal/modules/cleaner"
|
||||
"evening_detective/internal/modules/formatter"
|
||||
"evening_detective/internal/modules/password"
|
||||
"evening_detective/internal/services"
|
||||
"evening_detective/internal/services/db"
|
||||
"evening_detective/internal/services/link"
|
||||
"evening_detective/internal/services/pdf"
|
||||
"evening_detective/internal/services/story_service"
|
||||
"evening_detective/internal/services/story_storage"
|
||||
proto "evening_detective/proto"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
//go:embed static/user/*
|
||||
var userFS embed.FS
|
||||
|
||||
//go:embed static/admin/*
|
||||
var adminFS embed.FS
|
||||
|
||||
func main() {
|
||||
// Create a listener on TCP port
|
||||
lis, err := net.Listen("tcp", ":8080")
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to listen:", err)
|
||||
}
|
||||
grpcGatewayHost := config.GetGrpcGatewayHost()
|
||||
userClientHost := config.GetUserClientHost()
|
||||
adminClientHost := config.GetAdminClientHost()
|
||||
fileHost := config.GetFileHost()
|
||||
|
||||
// Create a gRPC server object
|
||||
s := grpc.NewServer()
|
||||
// Attach the Greeter service to the server
|
||||
proto.RegisterEveningDetectiveServer(s, app.NewServer())
|
||||
// Serve gRPC server
|
||||
log.Println("Serving gRPC on 0.0.0.0:8080")
|
||||
dbFilepath := config.GetDBFilepath()
|
||||
|
||||
dbService, err := db.NewDBService(dbFilepath)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
cleaner := cleaner.NewCleaner()
|
||||
|
||||
formatter := formatter.NewFormatter()
|
||||
|
||||
storyFilepath := config.GetStoryFilepath()
|
||||
|
||||
storyStorage := story_storage.NewFileStoryStorage(storyFilepath)
|
||||
|
||||
storyService, err := story_service.NewStoryService(
|
||||
cleaner,
|
||||
formatter,
|
||||
storyStorage,
|
||||
link.NewLinkService(fileHost),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
linkService := link.NewLinkService(userClientHost)
|
||||
|
||||
passwordGenerator := password.NewPasswordGenerator()
|
||||
|
||||
pdfGenerator := pdf.NewPDFGenerator()
|
||||
|
||||
proto.RegisterEveningDetectiveServer(
|
||||
s,
|
||||
app.NewServer(
|
||||
services.NewServices(
|
||||
dbService,
|
||||
storyService,
|
||||
linkService,
|
||||
passwordGenerator,
|
||||
pdfGenerator,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// Server gRPC
|
||||
lis, err := net.Listen("tcp", ":8080")
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to listen:", err)
|
||||
}
|
||||
go func() {
|
||||
log.Fatalln(s.Serve(lis))
|
||||
}()
|
||||
|
||||
// Client gRPC
|
||||
// Create a client connection to the gRPC server we just started
|
||||
// This is where the gRPC-Gateway proxies the requests
|
||||
conn, err := grpc.NewClient(
|
||||
@@ -40,39 +106,90 @@ func main() {
|
||||
log.Fatalln("Failed to dial server:", err)
|
||||
}
|
||||
|
||||
gwmux := runtime.NewServeMux()
|
||||
gwmux := runtime.NewServeMux(
|
||||
runtime.WithMetadata(func(ctx context.Context, request *http.Request) metadata.MD {
|
||||
teamId := request.Header.Get("X-Id")
|
||||
return metadata.Pairs("team-id", teamId)
|
||||
}),
|
||||
runtime.WithMetadata(func(ctx context.Context, request *http.Request) metadata.MD {
|
||||
password := request.Header.Get("X-Password")
|
||||
return metadata.Pairs("password", password)
|
||||
}),
|
||||
)
|
||||
// Register Greeter
|
||||
err = proto.RegisterEveningDetectiveHandler(context.Background(), gwmux, conn)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to register gateway:", err)
|
||||
}
|
||||
|
||||
// Server gRPC-Gateway
|
||||
gwServer := &http.Server{
|
||||
Addr: ":8090",
|
||||
Handler: gwmux,
|
||||
Addr: config.GrpcGatewayPort,
|
||||
Handler: cors(gwmux),
|
||||
}
|
||||
|
||||
// Serve gRPC-Gateway server
|
||||
log.Println("Serving gRPC-Gateway on http://0.0.0.0:8090")
|
||||
log.Printf("Serving %s for gRPC-Gateway\n", grpcGatewayHost)
|
||||
go func() {
|
||||
log.Fatalln(gwServer.ListenAndServe())
|
||||
}()
|
||||
|
||||
muxUser := http.NewServeMux()
|
||||
fileServerUser := http.FileServer(http.Dir("./static/user"))
|
||||
subUserFS, err := fs.Sub(userFS, "static/user")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
fileServerUser := http.FileServer(http.FS(subUserFS))
|
||||
muxUser.Handle("/", fileServerUser)
|
||||
|
||||
// Serve user web server
|
||||
log.Println("Serving user web on http://0.0.0.0:8100")
|
||||
// Server user web
|
||||
log.Printf("Serving %s for user web\n", userClientHost)
|
||||
go func() {
|
||||
log.Fatalln(http.ListenAndServe(":8100", muxUser))
|
||||
log.Fatalln(http.ListenAndServe(config.UserClientPort, muxUser))
|
||||
}()
|
||||
|
||||
go func() {
|
||||
dir := "./data/story/images"
|
||||
fs := http.FileServer(http.Dir(dir))
|
||||
http.Handle("/", loggingMiddleware(fs))
|
||||
log.Printf("Serving %s for file server, directory: %s\n", fileHost, dir)
|
||||
log.Fatal(http.ListenAndServe(config.FilePort, nil))
|
||||
}()
|
||||
|
||||
muxAdmin := http.NewServeMux()
|
||||
fileServerAdmin := http.FileServer(http.Dir("./static/admin"))
|
||||
subAdminFS, err := fs.Sub(adminFS, "static/admin")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
fileServerAdmin := http.FileServer(http.FS(subAdminFS))
|
||||
muxAdmin.Handle("/", fileServerAdmin)
|
||||
|
||||
// Serve admin web server
|
||||
log.Println("Serving admin web on http://0.0.0.0:8110")
|
||||
log.Fatalln(http.ListenAndServe(":8110", muxAdmin))
|
||||
// Server admin web
|
||||
adminWebServer := &http.Server{
|
||||
Addr: config.AdminClientPort,
|
||||
Handler: muxAdmin,
|
||||
}
|
||||
log.Printf("Serving %s for admin web \n", adminClientHost)
|
||||
log.Fatalln(adminWebServer.ListenAndServe())
|
||||
}
|
||||
|
||||
func cors(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, Authorization, ResponseType, X-Id, X-Password")
|
||||
if r.Method == "OPTIONS" {
|
||||
return
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func loggingMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
|
||||
// Логируем запрос
|
||||
log.Printf("[%s] %s %s", r.Method, r.URL.Path, time.Since(start))
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
@@ -4,9 +4,9 @@
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
<script type="module" crossorigin src="/assets/index-waNsD4Su.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DAw4mc7B.css">
|
||||
<title>ВД Админка</title>
|
||||
<script type="module" crossorigin src="/assets/index-c8po_p3Q.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CgpxTv-m.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
After Width: | Height: | Size: 442 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 802 KiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 290 KiB |
|
After Width: | Height: | Size: 326 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 66 KiB |
@@ -4,9 +4,9 @@
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
<script type="module" crossorigin src="/assets/index-BKrme-n6.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BuR9UoUk.css">
|
||||
<title>Вечерний детектив</title>
|
||||
<script type="module" crossorigin src="/assets/index-CQcj9qbi.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-C2dfznw-.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
@@ -0,0 +1,68 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type story struct {
|
||||
Places []placeBlock `json:"places"`
|
||||
}
|
||||
|
||||
type placeBlock struct {
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Text string `json:"text"`
|
||||
Applications []application `json:"applications,omitempty"`
|
||||
}
|
||||
|
||||
type application struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
b, err := os.ReadFile("./cmd/text_to_story/text.txt")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
story := &story{}
|
||||
lines := strings.Split(string(b), "\n")
|
||||
var place *placeBlock
|
||||
text := ""
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "[") {
|
||||
if place != nil {
|
||||
place.Text = strings.TrimSpace(text)
|
||||
story.Places = append(story.Places, *place)
|
||||
text = ""
|
||||
}
|
||||
place = &placeBlock{}
|
||||
codeAndName := strings.Split(line, " ")
|
||||
place.Code = strings.Trim(codeAndName[0], "[]")
|
||||
place.Name = strings.TrimSpace(strings.TrimPrefix(strings.Join(codeAndName[1:], " "), "-"))
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "Приложение:") {
|
||||
place.Applications = append(
|
||||
place.Applications,
|
||||
application{
|
||||
Name: strings.TrimSpace(strings.TrimPrefix(line, "Приложение:")),
|
||||
},
|
||||
)
|
||||
continue
|
||||
}
|
||||
text += line + "\n"
|
||||
}
|
||||
place.Text = strings.TrimSpace(text)
|
||||
story.Places = append(story.Places, *place)
|
||||
|
||||
res, err := json.Marshal(story)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := os.WriteFile("./cmd/text_to_story/story.json", res, 0x777); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,47 @@ services:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "8080:8080"
|
||||
# environment:
|
||||
# - ENV_VAR_NAME=env_var_value
|
||||
- "8090:8090" # api
|
||||
- "8100:8100" # user
|
||||
- "8110:8110" # admin
|
||||
- "8120:8120" # files
|
||||
environment:
|
||||
- HOST=https://evening-detective.crabs-games.art
|
||||
- FILE_HOST=https://evening-detective-files.crabs-games.art
|
||||
networks:
|
||||
- crabs-network
|
||||
volumes:
|
||||
- ./data:/data
|
||||
labels:
|
||||
# api
|
||||
reproxy.1.server: "evening-detective-api.crabs-games.art"
|
||||
reproxy.1.route: "/(.*)"
|
||||
reproxy.1.dest: "http://evening_detective:8090/$$1"
|
||||
reproxy.1.port: "8090"
|
||||
reproxy.1.ping: "/"
|
||||
|
||||
# user
|
||||
reproxy.2.server: "evening-detective.crabs-games.art"
|
||||
reproxy.2.route: "/(.*)"
|
||||
reproxy.2.dest: "http://evening_detective:8100/$$1"
|
||||
reproxy.2.port: "8100"
|
||||
reproxy.2.ping: "/"
|
||||
|
||||
# admin
|
||||
reproxy.3.server: "evening-detective-admin.crabs-games.art"
|
||||
reproxy.3.route: "/(.*)"
|
||||
reproxy.3.dest: "http://evening_detective:8110/$$1"
|
||||
reproxy.3.port: "8110"
|
||||
reproxy.3.ping: "/"
|
||||
|
||||
# files
|
||||
reproxy.4.server: "evening-detective-files.crabs-games.art"
|
||||
reproxy.4.route: "/(.*)"
|
||||
reproxy.4.dest: "http://evening_detective:8120/$$1"
|
||||
reproxy.4.port: "8120"
|
||||
reproxy.4.ping: "/"
|
||||
|
||||
networks:
|
||||
crabs-network:
|
||||
name: crabs-network
|
||||
external: true
|
||||
|
||||
@@ -1,17 +1,35 @@
|
||||
module evening_detective
|
||||
|
||||
go 1.22
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0
|
||||
google.golang.org/grpc v1.64.0
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2
|
||||
google.golang.org/grpc v1.75.0
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/net v0.23.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 // indirect
|
||||
github.com/pkg/errors v0.8.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/stretchr/testify v1.10.0
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/mattn/go-sqlite3 v1.14.28
|
||||
github.com/signintech/gopdf v0.32.0
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
|
||||
google.golang.org/protobuf v1.36.7
|
||||
)
|
||||
|
||||
@@ -2,52 +2,72 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"evening_detective/internal/services"
|
||||
proto "evening_detective/proto"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
proto.UnimplementedEveningDetectiveServer
|
||||
|
||||
services *services.Services
|
||||
}
|
||||
|
||||
func NewServer() *Server {
|
||||
return &Server{}
|
||||
func NewServer(
|
||||
services *services.Services,
|
||||
) *Server {
|
||||
return &Server{
|
||||
services: services,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Ping(_ context.Context, _ *proto.PingReq) (*proto.PingRsp, error) {
|
||||
return &proto.PingRsp{}, nil
|
||||
}
|
||||
|
||||
func (s *Server) AddTeams(context.Context, *proto.AddTeamsReq) (*proto.AddTeamsRsp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AddTeams not implemented")
|
||||
func (s *Server) AddTeams(ctx context.Context, req *proto.AddTeamsReq) (*proto.AddTeamsRsp, error) {
|
||||
return s.services.AddTeams(ctx, req)
|
||||
}
|
||||
|
||||
func (s *Server) GetTeams(context.Context, *proto.GetTeamsReq) (*proto.GetTeamsRsp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetTeams not implemented")
|
||||
func (s *Server) GetTeams(ctx context.Context, req *proto.GetTeamsReq) (*proto.GetTeamsRsp, error) {
|
||||
return s.services.GetTeams(ctx, req)
|
||||
}
|
||||
|
||||
func (s *Server) GetTeamsCSV(context.Context, *proto.GetTeamsCSVReq) (*proto.GetTeamsCSVRsp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetTeamsCSV not implemented")
|
||||
func (s *Server) GetTeamsCSV(ctx context.Context, req *proto.GetTeamsCSVReq) (*proto.GetTeamsCSVRsp, error) {
|
||||
return s.services.GetTeamsCSV(ctx, req)
|
||||
}
|
||||
|
||||
func (s *Server) DeleteTeams(context.Context, *proto.DeleteTeamsReq) (*proto.DeleteTeamsRsp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteTeams not implemented")
|
||||
func (s *Server) GetTeam(ctx context.Context, req *proto.GetTeamReq) (*proto.GetTeamRsp, error) {
|
||||
return s.services.GetTeam(ctx, req)
|
||||
}
|
||||
|
||||
func (s *Server) AddAction(context.Context, *proto.AddActionReq) (*proto.AddActionRsp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AddAction not implemented")
|
||||
func (s *Server) AddAction(ctx context.Context, req *proto.AddActionReq) (*proto.AddActionRsp, error) {
|
||||
return s.services.AddAction(ctx, req)
|
||||
}
|
||||
|
||||
func (s *Server) GameStart(context.Context, *proto.GameStartReq) (*proto.GameStartRsp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GameStart not implemented")
|
||||
func (s *Server) GetGame(ctx context.Context, req *proto.GetGameReq) (*proto.GetGameRsp, error) {
|
||||
return s.services.GetGame(ctx, req)
|
||||
}
|
||||
|
||||
func (s *Server) GameStop(context.Context, *proto.GameStopReq) (*proto.GameStopRsp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GameStop not implemented")
|
||||
func (s *Server) GameStart(ctx context.Context, req *proto.GameStartReq) (*proto.GameStartRsp, error) {
|
||||
return s.services.GameStart(ctx, req)
|
||||
}
|
||||
|
||||
func (s *Server) GiveApplications(context.Context, *proto.GiveApplicationsReq) (*proto.GiveApplicationsRsp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GiveApplications not implemented")
|
||||
func (s *Server) GameStop(ctx context.Context, req *proto.GameStopReq) (*proto.GameStopRsp, error) {
|
||||
return s.services.GameStop(ctx, req)
|
||||
}
|
||||
|
||||
func (s *Server) GiveApplications(ctx context.Context, req *proto.GiveApplicationsReq) (*proto.GiveApplicationsRsp, error) {
|
||||
return s.services.GiveApplications(ctx, req)
|
||||
}
|
||||
|
||||
func (s *Server) DownloadTeamsQrCodesFile(ctx context.Context, req *proto.DownloadTeamsQrCodesFileReq) (*proto.DownloadTeamsQrCodesFileRsp, error) {
|
||||
return s.services.DownloadTeamsQrCodesFile(ctx, req)
|
||||
}
|
||||
|
||||
func (s *Server) GetGraph(ctx context.Context, req *proto.GetGraphReq) (*proto.GetGraphRsp, error) {
|
||||
return s.services.GetGraph(ctx, req)
|
||||
}
|
||||
|
||||
func (s *Server) UpdateNode(ctx context.Context, req *proto.UpdateNodeReq) (*proto.UpdateNodeRsp, error) {
|
||||
return s.services.UpdateNode(ctx, req)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
GrpcGatewayPort = ":8090"
|
||||
UserClientPort = ":8100"
|
||||
AdminClientPort = ":8110"
|
||||
FilePort = ":8120"
|
||||
)
|
||||
|
||||
func GetStoryFilepath() string {
|
||||
return getFilepath("STORY_FILENAME", "data/story/story.json")
|
||||
}
|
||||
|
||||
func GetDBFilepath() string {
|
||||
return getFilepath("DB_FILENAME", "data/db/store.db")
|
||||
}
|
||||
|
||||
func GetGrpcGatewayHost() string {
|
||||
host := os.Getenv("HOST")
|
||||
if host != "" {
|
||||
return host
|
||||
}
|
||||
ips, err := getLocalIPs()
|
||||
if err != nil || len(ips) == 0 {
|
||||
return "http://127.0.0.1" + GrpcGatewayPort
|
||||
}
|
||||
return "http://" + ips[0] + GrpcGatewayPort
|
||||
}
|
||||
|
||||
func GetAdminClientHost() string {
|
||||
host := os.Getenv("HOST")
|
||||
if host != "" {
|
||||
return host
|
||||
}
|
||||
ips, err := getLocalIPs()
|
||||
if err != nil || len(ips) == 0 {
|
||||
return "http://127.0.0.1" + AdminClientPort
|
||||
}
|
||||
return "http://" + ips[0] + AdminClientPort
|
||||
}
|
||||
|
||||
func GetUserClientHost() string {
|
||||
host := os.Getenv("HOST")
|
||||
if host != "" {
|
||||
return host
|
||||
}
|
||||
ips, err := getLocalIPs()
|
||||
if err != nil || len(ips) == 0 {
|
||||
return "http://127.0.0.1" + UserClientPort
|
||||
}
|
||||
return "http://" + ips[0] + UserClientPort
|
||||
}
|
||||
|
||||
func GetFileHost() string {
|
||||
host := os.Getenv("FILE_HOST")
|
||||
if host != "" {
|
||||
return host
|
||||
}
|
||||
ips, err := getLocalIPs()
|
||||
if err != nil || len(ips) == 0 {
|
||||
return "http://127.0.0.1" + FilePort
|
||||
}
|
||||
return "http://" + ips[0] + FilePort
|
||||
}
|
||||
|
||||
func getFilepath(env string, defaultFilepath string) string {
|
||||
filepath := selectFilepath(env, defaultFilepath)
|
||||
ensureDirExists(filepath)
|
||||
return filepath
|
||||
}
|
||||
|
||||
func selectFilepath(env string, defaultFilepath string) string {
|
||||
filepath := os.Getenv(env)
|
||||
if filepath != "" {
|
||||
return filepath
|
||||
}
|
||||
return defaultFilepath
|
||||
}
|
||||
|
||||
func ensureDirExists(filePath string) error {
|
||||
dir := filepath.Dir(filePath)
|
||||
if dir == "" || dir == "." || dir == "/" {
|
||||
return nil
|
||||
}
|
||||
return os.MkdirAll(dir, 0755)
|
||||
}
|
||||
|
||||
func getLocalIPs() ([]string, error) {
|
||||
var ips []string
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
ipNet, ok := addr.(*net.IPNet)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ip := ipNet.IP
|
||||
if ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() {
|
||||
continue
|
||||
}
|
||||
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
ips = append(ips, ipv4.String())
|
||||
}
|
||||
}
|
||||
|
||||
return ips, nil
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package models
|
||||
|
||||
type Action struct {
|
||||
ID int64
|
||||
Place string
|
||||
TeamID int64
|
||||
Applications []*Application
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package models
|
||||
|
||||
type Application struct {
|
||||
ID int64
|
||||
TeamID int64
|
||||
Name string
|
||||
State string
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package models
|
||||
|
||||
type Game struct {
|
||||
State string
|
||||
StartTime string
|
||||
EndTime string
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package models
|
||||
|
||||
type Team struct {
|
||||
ID int64
|
||||
Name string
|
||||
Password string
|
||||
Link string
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package cleaner
|
||||
|
||||
type ICleaner interface {
|
||||
ClearCode(code string) string
|
||||
ClearText(text string) string
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package cleaner
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
replaceMap = map[string]string{
|
||||
"a": "а",
|
||||
"e": "е",
|
||||
"o": "о",
|
||||
"c": "с",
|
||||
"p": "р",
|
||||
"x": "х",
|
||||
"y": "у",
|
||||
"k": "к",
|
||||
"m": "м",
|
||||
"t": "т",
|
||||
"h": "н",
|
||||
"b": "в",
|
||||
"u": "и",
|
||||
}
|
||||
re = regexp.MustCompile(`\(\[[a-zA-Zа-яА-Я\d-]+\]\)`)
|
||||
)
|
||||
|
||||
type service struct{}
|
||||
|
||||
func NewCleaner() ICleaner {
|
||||
return &service{}
|
||||
}
|
||||
|
||||
func (s *service) ClearCode(code string) string {
|
||||
code = strings.TrimPrefix(code, "(")
|
||||
code = strings.TrimPrefix(code, "[")
|
||||
code = strings.TrimSuffix(code, ")")
|
||||
code = strings.TrimSuffix(code, "]")
|
||||
code = strings.ToLower(code)
|
||||
code = strings.TrimSpace(code)
|
||||
code = strings.ReplaceAll(code, "-", "")
|
||||
for latin, cyrillic := range replaceMap {
|
||||
code = strings.ReplaceAll(code, latin, cyrillic)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
func (s *service) ClearText(text string) string {
|
||||
return strings.TrimSpace(re.ReplaceAllString(text, ""))
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package cleaner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_service_ClearCode(t *testing.T) {
|
||||
tests := []struct {
|
||||
code string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
code: "ы",
|
||||
want: "ы",
|
||||
},
|
||||
{
|
||||
code: "Ы",
|
||||
want: "ы",
|
||||
},
|
||||
{
|
||||
code: "Ы-1",
|
||||
want: "ы1",
|
||||
},
|
||||
{
|
||||
code: "[Ы]",
|
||||
want: "ы",
|
||||
},
|
||||
{
|
||||
code: "([Ы])",
|
||||
want: "ы",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%s->%s", tt.code, tt.want), func(t *testing.T) {
|
||||
var s service
|
||||
got := s.ClearCode(tt.code)
|
||||
if got != tt.want {
|
||||
t.Errorf("ClearCode() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_service_ClearText(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "text",
|
||||
want: "text",
|
||||
},
|
||||
{
|
||||
text: "text ([Ы])",
|
||||
want: "text",
|
||||
},
|
||||
{
|
||||
text: "text ([Ы-3])",
|
||||
want: "text",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.text, func(t *testing.T) {
|
||||
var s service
|
||||
got := s.ClearText(tt.text)
|
||||
if got != tt.want {
|
||||
t.Errorf("ClearText() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package formatter
|
||||
|
||||
type IFormatter interface {
|
||||
FormatText(text string) string
|
||||
FormatString(text string) string
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type service struct{}
|
||||
|
||||
func NewFormatter() IFormatter {
|
||||
return &service{}
|
||||
}
|
||||
|
||||
func (s *service) FormatText(text string) string {
|
||||
scanner := bufio.NewScanner(strings.NewReader(text))
|
||||
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
lines := []string{}
|
||||
for scanner.Scan() {
|
||||
lines = append(lines, scanner.Text())
|
||||
}
|
||||
|
||||
var res strings.Builder
|
||||
for i, line := range lines {
|
||||
l := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(l, "--") {
|
||||
l = strings.Replace(l, "--", "—", 1)
|
||||
}
|
||||
if i == 0 && strings.HasPrefix(l, "—") {
|
||||
res.WriteString(" ")
|
||||
}
|
||||
if i > 0 {
|
||||
res.WriteString("\n")
|
||||
if len(l) > 0 {
|
||||
res.WriteString(" ")
|
||||
}
|
||||
}
|
||||
res.WriteString(l)
|
||||
}
|
||||
return res.String()
|
||||
}
|
||||
|
||||
func (s *service) FormatString(text string) string {
|
||||
l := strings.TrimSpace(text)
|
||||
if strings.HasPrefix(l, "--") {
|
||||
l = strings.Replace(l, "--", "—", 1)
|
||||
}
|
||||
return l
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package formatter
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_service_FormatText(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Простой текст",
|
||||
text: "Привет",
|
||||
want: "Привет",
|
||||
},
|
||||
{
|
||||
name: "Текст с двумя абзацами",
|
||||
text: "Привет\nМир",
|
||||
want: "Привет\n Мир",
|
||||
},
|
||||
{
|
||||
name: "Прямая речь",
|
||||
text: "— Привет",
|
||||
want: " — Привет",
|
||||
},
|
||||
{
|
||||
name: "Прямая речь через 2 минуса",
|
||||
text: "-- Привет",
|
||||
want: " — Привет",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var s service
|
||||
got := s.FormatText(tt.text)
|
||||
if got != tt.want {
|
||||
t.Errorf("FormatText() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package password
|
||||
|
||||
type IPasswordGenerator interface {
|
||||
GeneratePassword(length int) string
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package password
|
||||
|
||||
import "math/rand"
|
||||
|
||||
var (
|
||||
letters = []rune("abcdefghijklmnopqrstuvwxyz123456789")
|
||||
)
|
||||
|
||||
type service struct{}
|
||||
|
||||
func NewPasswordGenerator() IPasswordGenerator {
|
||||
return &service{}
|
||||
}
|
||||
|
||||
func (s *service) GeneratePassword(length int) string {
|
||||
b := make([]rune, length)
|
||||
for i := range b {
|
||||
b[i] = letters[rand.Intn(len(letters))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//go:generate mockgen -source=interface.go -destination=mocks/mock.go -package=mocks
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"evening_detective/internal/models"
|
||||
)
|
||||
|
||||
type IDBService interface {
|
||||
Close()
|
||||
|
||||
GetTeams(ctx context.Context) ([]*models.Team, error)
|
||||
GetTeam(ctx context.Context, teamId any, password any) (*models.Team, error)
|
||||
AddTeams(ctx context.Context, teams []*models.Team) ([]*models.Team, error)
|
||||
DeleteAllTeams(ctx context.Context) error
|
||||
|
||||
GetActions(ctx context.Context, teamId int64) ([]*models.Action, error)
|
||||
AddActions(ctx context.Context, teamId int64, actions []*models.Action) error
|
||||
|
||||
GetApplications(ctx context.Context, teamId int64) ([]*models.Application, error)
|
||||
GetApplicationsByState(ctx context.Context, teamId int64, state string) ([]*models.Application, error)
|
||||
AddApplications(ctx context.Context, teamId int64, applications []*models.Application) error
|
||||
|
||||
GiveApplications(ctx context.Context, teamId int64, applications []*models.Application) error
|
||||
|
||||
GetGame(ctx context.Context) (*models.Game, error)
|
||||
UpdateGameState(ctx context.Context, state string) error
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: interface.go
|
||||
|
||||
// Package mocks is a generated GoMock package.
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
models "evening_detective/internal/models"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockIDBService is a mock of IDBService interface.
|
||||
type MockIDBService struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockIDBServiceMockRecorder
|
||||
}
|
||||
|
||||
// MockIDBServiceMockRecorder is the mock recorder for MockIDBService.
|
||||
type MockIDBServiceMockRecorder struct {
|
||||
mock *MockIDBService
|
||||
}
|
||||
|
||||
// NewMockIDBService creates a new mock instance.
|
||||
func NewMockIDBService(ctrl *gomock.Controller) *MockIDBService {
|
||||
mock := &MockIDBService{ctrl: ctrl}
|
||||
mock.recorder = &MockIDBServiceMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockIDBService) EXPECT() *MockIDBServiceMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// AddActions mocks base method.
|
||||
func (m *MockIDBService) AddActions(ctx context.Context, teamId int64, actions []*models.Action) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddActions", ctx, teamId, actions)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddActions indicates an expected call of AddActions.
|
||||
func (mr *MockIDBServiceMockRecorder) AddActions(ctx, teamId, actions interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddActions", reflect.TypeOf((*MockIDBService)(nil).AddActions), ctx, teamId, actions)
|
||||
}
|
||||
|
||||
// AddApplications mocks base method.
|
||||
func (m *MockIDBService) AddApplications(ctx context.Context, teamId int64, applications []*models.Application) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddApplications", ctx, teamId, applications)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddApplications indicates an expected call of AddApplications.
|
||||
func (mr *MockIDBServiceMockRecorder) AddApplications(ctx, teamId, applications interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddApplications", reflect.TypeOf((*MockIDBService)(nil).AddApplications), ctx, teamId, applications)
|
||||
}
|
||||
|
||||
// AddTeams mocks base method.
|
||||
func (m *MockIDBService) AddTeams(ctx context.Context, teams []*models.Team) ([]*models.Team, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddTeams", ctx, teams)
|
||||
ret0, _ := ret[0].([]*models.Team)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AddTeams indicates an expected call of AddTeams.
|
||||
func (mr *MockIDBServiceMockRecorder) AddTeams(ctx, teams interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddTeams", reflect.TypeOf((*MockIDBService)(nil).AddTeams), ctx, teams)
|
||||
}
|
||||
|
||||
// Close mocks base method.
|
||||
func (m *MockIDBService) Close() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Close")
|
||||
}
|
||||
|
||||
// Close indicates an expected call of Close.
|
||||
func (mr *MockIDBServiceMockRecorder) Close() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockIDBService)(nil).Close))
|
||||
}
|
||||
|
||||
// DeleteAllTeams mocks base method.
|
||||
func (m *MockIDBService) DeleteAllTeams(ctx context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteAllTeams", ctx)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteAllTeams indicates an expected call of DeleteAllTeams.
|
||||
func (mr *MockIDBServiceMockRecorder) DeleteAllTeams(ctx interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllTeams", reflect.TypeOf((*MockIDBService)(nil).DeleteAllTeams), ctx)
|
||||
}
|
||||
|
||||
// GetActions mocks base method.
|
||||
func (m *MockIDBService) GetActions(ctx context.Context, teamId int64) ([]*models.Action, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetActions", ctx, teamId)
|
||||
ret0, _ := ret[0].([]*models.Action)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetActions indicates an expected call of GetActions.
|
||||
func (mr *MockIDBServiceMockRecorder) GetActions(ctx, teamId interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActions", reflect.TypeOf((*MockIDBService)(nil).GetActions), ctx, teamId)
|
||||
}
|
||||
|
||||
// GetApplications mocks base method.
|
||||
func (m *MockIDBService) GetApplications(ctx context.Context, teamId int64) ([]*models.Application, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetApplications", ctx, teamId)
|
||||
ret0, _ := ret[0].([]*models.Application)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetApplications indicates an expected call of GetApplications.
|
||||
func (mr *MockIDBServiceMockRecorder) GetApplications(ctx, teamId interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetApplications", reflect.TypeOf((*MockIDBService)(nil).GetApplications), ctx, teamId)
|
||||
}
|
||||
|
||||
// GetApplicationsByState mocks base method.
|
||||
func (m *MockIDBService) GetApplicationsByState(ctx context.Context, teamId int64, state string) ([]*models.Application, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetApplicationsByState", ctx, teamId, state)
|
||||
ret0, _ := ret[0].([]*models.Application)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetApplicationsByState indicates an expected call of GetApplicationsByState.
|
||||
func (mr *MockIDBServiceMockRecorder) GetApplicationsByState(ctx, teamId, state interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetApplicationsByState", reflect.TypeOf((*MockIDBService)(nil).GetApplicationsByState), ctx, teamId, state)
|
||||
}
|
||||
|
||||
// GetGame mocks base method.
|
||||
func (m *MockIDBService) GetGame(ctx context.Context) (*models.Game, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetGame", ctx)
|
||||
ret0, _ := ret[0].(*models.Game)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetGame indicates an expected call of GetGame.
|
||||
func (mr *MockIDBServiceMockRecorder) GetGame(ctx interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGame", reflect.TypeOf((*MockIDBService)(nil).GetGame), ctx)
|
||||
}
|
||||
|
||||
// GetTeam mocks base method.
|
||||
func (m *MockIDBService) GetTeam(ctx context.Context, teamId, password any) (*models.Team, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetTeam", ctx, teamId, password)
|
||||
ret0, _ := ret[0].(*models.Team)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetTeam indicates an expected call of GetTeam.
|
||||
func (mr *MockIDBServiceMockRecorder) GetTeam(ctx, teamId, password interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTeam", reflect.TypeOf((*MockIDBService)(nil).GetTeam), ctx, teamId, password)
|
||||
}
|
||||
|
||||
// GetTeams mocks base method.
|
||||
func (m *MockIDBService) GetTeams(ctx context.Context) ([]*models.Team, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetTeams", ctx)
|
||||
ret0, _ := ret[0].([]*models.Team)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetTeams indicates an expected call of GetTeams.
|
||||
func (mr *MockIDBServiceMockRecorder) GetTeams(ctx interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTeams", reflect.TypeOf((*MockIDBService)(nil).GetTeams), ctx)
|
||||
}
|
||||
|
||||
// GiveApplications mocks base method.
|
||||
func (m *MockIDBService) GiveApplications(ctx context.Context, teamId int64, applications []*models.Application) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GiveApplications", ctx, teamId, applications)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GiveApplications indicates an expected call of GiveApplications.
|
||||
func (mr *MockIDBServiceMockRecorder) GiveApplications(ctx, teamId, applications interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GiveApplications", reflect.TypeOf((*MockIDBService)(nil).GiveApplications), ctx, teamId, applications)
|
||||
}
|
||||
|
||||
// UpdateGameState mocks base method.
|
||||
func (m *MockIDBService) UpdateGameState(ctx context.Context, state string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateGameState", ctx, state)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpdateGameState indicates an expected call of UpdateGameState.
|
||||
func (mr *MockIDBServiceMockRecorder) UpdateGameState(ctx, state interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateGameState", reflect.TypeOf((*MockIDBService)(nil).UpdateGameState), ctx, state)
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"evening_detective/internal/models"
|
||||
"log"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewDBService(filepath string) (IDBService, error) {
|
||||
db, err := sql.Open("sqlite3", filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("Load db from: %s", filepath)
|
||||
_, err = db.Exec("CREATE TABLE IF NOT EXISTS teams (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL CHECK(length(trim(name)) > 0), password TEXT);")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = db.Exec("CREATE TABLE IF NOT EXISTS actions (id INTEGER PRIMARY KEY AUTOINCREMENT, place TEXT, teamId INTEGER, FOREIGN KEY (teamId) REFERENCES teams(id) ON DELETE CASCADE);")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = db.Exec("CREATE TABLE IF NOT EXISTS applications (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, teamId INTEGER, state TEXT, FOREIGN KEY (teamId) REFERENCES teams(id) ON DELETE CASCADE);")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = db.Exec("CREATE TABLE IF NOT EXISTS games (id INTEGER PRIMARY KEY AUTOINCREMENT, state TEXT, startAt TEXT, endAt TEXT);")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &service{db: db}, nil
|
||||
}
|
||||
|
||||
func (s *service) Close() {
|
||||
s.db.Close()
|
||||
}
|
||||
|
||||
func (s *service) GetTeams(ctx context.Context) ([]*models.Team, error) {
|
||||
rows, err := s.db.Query("select id, name, password from teams")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
teams := []*models.Team{}
|
||||
|
||||
for rows.Next() {
|
||||
item := &models.Team{}
|
||||
err := rows.Scan(&item.ID, &item.Name, &item.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
teams = append(teams, item)
|
||||
}
|
||||
return teams, nil
|
||||
}
|
||||
|
||||
func (s *service) GetTeam(ctx context.Context, teamId any, password any) (*models.Team, error) {
|
||||
rows, err := s.db.Query("select id, name from teams where LOWER(name) = LOWER($1) and password = $2", teamId, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
teams := []*models.Team{}
|
||||
|
||||
for rows.Next() {
|
||||
item := &models.Team{}
|
||||
err := rows.Scan(&item.ID, &item.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
teams = append(teams, item)
|
||||
}
|
||||
if len(teams) != 1 {
|
||||
return nil, errors.New("bad result")
|
||||
}
|
||||
return teams[0], nil
|
||||
}
|
||||
|
||||
func (s *service) AddTeams(ctx context.Context, teams []*models.Team) ([]*models.Team, error) {
|
||||
for _, team := range teams {
|
||||
result, err := s.db.Exec("insert into teams (name, password) values ($1, $2)", team.Name, team.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
team.ID, err = result.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return teams, nil
|
||||
}
|
||||
|
||||
func (s *service) DeleteAllTeams(ctx context.Context) error {
|
||||
_, err := s.db.Exec("delete from teams where 1")
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *service) GetActions(ctx context.Context, teamId int64) ([]*models.Action, error) {
|
||||
rows, err := s.db.Query("select id, place from actions where teamId = $1", teamId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
actions := []*models.Action{}
|
||||
|
||||
for rows.Next() {
|
||||
item := &models.Action{}
|
||||
err := rows.Scan(&item.ID, &item.Place)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
actions = append(actions, item)
|
||||
}
|
||||
return actions, nil
|
||||
}
|
||||
|
||||
func (s *service) AddActions(ctx context.Context, teamId int64, actions []*models.Action) error {
|
||||
for _, action := range actions {
|
||||
_, err := s.db.Exec("insert into actions (place, teamId) values ($1, $2)", action.Place, teamId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) GetApplications(ctx context.Context, teamId int64) ([]*models.Application, error) {
|
||||
rows, err := s.db.Query("select id, name from applications where teamId = $1", teamId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
applications := []*models.Application{}
|
||||
|
||||
for rows.Next() {
|
||||
item := &models.Application{}
|
||||
err := rows.Scan(&item.ID, &item.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
applications = append(applications, item)
|
||||
}
|
||||
return applications, nil
|
||||
}
|
||||
|
||||
func (s *service) GetApplicationsByState(ctx context.Context, teamId int64, state string) ([]*models.Application, error) {
|
||||
rows, err := s.db.Query("select id, name from applications where teamId = $1 and state = $2", teamId, state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
applications := []*models.Application{}
|
||||
|
||||
for rows.Next() {
|
||||
item := &models.Application{}
|
||||
err := rows.Scan(&item.ID, &item.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
applications = append(applications, item)
|
||||
}
|
||||
return applications, nil
|
||||
}
|
||||
|
||||
func (s *service) AddApplications(ctx context.Context, teamId int64, applications []*models.Application) error {
|
||||
for _, application := range applications {
|
||||
_, err := s.db.Exec("insert into applications (name, teamId, state) values ($1, $2, $3)", application.Name, teamId, application.State)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) GiveApplications(ctx context.Context, teamId int64, applications []*models.Application) error {
|
||||
for _, application := range applications {
|
||||
_, err := s.db.Exec("update applications set state = \"gave\" where teamId = $1 and id = $2", teamId, application.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) GetGame(ctx context.Context) (*models.Game, error) {
|
||||
rows, err := s.db.Query("select state, startAt, endAt from games limit 1")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
game := &models.Game{}
|
||||
if rows.Next() {
|
||||
err := rows.Scan(&game.State, &game.StartTime, &game.EndTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return game, nil
|
||||
}
|
||||
|
||||
state := "NEW"
|
||||
_, err = s.db.Exec("insert into games (state, startAt, endAt) values ($1, '', '')", state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
game.State = state
|
||||
return game, nil
|
||||
}
|
||||
|
||||
func (s *service) UpdateGameState(ctx context.Context, state string) error {
|
||||
game, err := s.GetGame(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch state {
|
||||
case "RUN":
|
||||
if game.StartTime == "" {
|
||||
_, err := s.db.Exec("update games set state = $1, startAt = datetime('now', 'localtime')", state)
|
||||
return err
|
||||
}
|
||||
_, err := s.db.Exec("update games set state = $1", state)
|
||||
return err
|
||||
case "STOP":
|
||||
_, err := s.db.Exec("update games set state = $1, endAt = datetime('now', 'localtime')", state)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package link
|
||||
|
||||
type ILinkService interface {
|
||||
GetTeamClientLink(name string, password string) string
|
||||
GetImageLink(name string) string
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package link
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
host string
|
||||
}
|
||||
|
||||
func NewLinkService(host string) ILinkService {
|
||||
return &service{
|
||||
host: host,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) GetTeamClientLink(name string, password string) string {
|
||||
return fmt.Sprintf("%s?name=%s&password=%s", s.host, url.PathEscape(name), password)
|
||||
}
|
||||
|
||||
func (s *service) GetImageLink(name string) string {
|
||||
if len(name) == 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s/%s", s.host, name)
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"evening_detective/internal/models"
|
||||
story_service_models "evening_detective/internal/services/story_service/models"
|
||||
"evening_detective/proto"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func mapTeamsToTeamAdvanced(team *models.Team) *proto.TeamAdvanced {
|
||||
return &proto.TeamAdvanced{
|
||||
Id: team.ID,
|
||||
Name: team.Name,
|
||||
Password: team.Password,
|
||||
}
|
||||
}
|
||||
|
||||
func mapTeamsToTeamFull(team *models.Team) *proto.TeamFull {
|
||||
return &proto.TeamFull{
|
||||
Id: team.ID,
|
||||
Name: team.Name,
|
||||
Password: team.Password,
|
||||
}
|
||||
}
|
||||
|
||||
func mapProtoTeamsToTeam(team *proto.Team) *models.Team {
|
||||
return &models.Team{
|
||||
Name: clearTeamName(team.Name),
|
||||
}
|
||||
}
|
||||
|
||||
func mapPlaceToProtoAction(place *story_service_models.Place) *proto.Action {
|
||||
return &proto.Action{
|
||||
Place: place.Code,
|
||||
}
|
||||
}
|
||||
|
||||
func mapStoryApplicationToProtoApplication(application *story_service_models.Application) *proto.Application {
|
||||
return &proto.Application{
|
||||
Name: application.Name,
|
||||
Number: application.Number,
|
||||
}
|
||||
}
|
||||
|
||||
func mapStoryDoorToProtoDoor(door *story_service_models.Door) *proto.Door {
|
||||
return &proto.Door{
|
||||
Code: door.Code,
|
||||
Name: door.Name,
|
||||
Show: door.Show,
|
||||
}
|
||||
}
|
||||
|
||||
func mapStoryApplicationsToApplications(applications []*story_service_models.Application) []*models.Application {
|
||||
res := make([]*models.Application, 0, len(applications))
|
||||
for _, application := range applications {
|
||||
res = append(res, mapStoryApplicationToApplication(application))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func mapStoryApplicationToApplication(application *story_service_models.Application) *models.Application {
|
||||
return &models.Application{
|
||||
Name: application.Name,
|
||||
State: "NEW",
|
||||
}
|
||||
}
|
||||
|
||||
func mapApplicationsToProtoApplications(applications []*models.Application) []*proto.Application {
|
||||
res := make([]*proto.Application, 0, len(applications))
|
||||
for _, application := range applications {
|
||||
res = append(res, mapApplicationToProtoApplication(application))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func mapApplicationToProtoApplication(application *models.Application) *proto.Application {
|
||||
return &proto.Application{
|
||||
Id: application.ID,
|
||||
Name: application.Name,
|
||||
State: application.State,
|
||||
}
|
||||
}
|
||||
|
||||
func mapProtoApplicationsToApplications(items []*proto.Application) []*models.Application {
|
||||
res := make([]*models.Application, 0, len(items))
|
||||
for _, item := range items {
|
||||
res = append(res, mapProtoApplicationToApplication(item))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func mapProtoApplicationToApplication(items *proto.Application) *models.Application {
|
||||
return &models.Application{
|
||||
ID: items.Id,
|
||||
}
|
||||
}
|
||||
|
||||
func clearTeamName(code string) string {
|
||||
return strings.TrimSpace(code)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package pdf
|
||||
|
||||
import "evening_detective/internal/models"
|
||||
|
||||
type IPDFGenerator interface {
|
||||
CreateTeamsPDF(teams []*models.Team) ([]byte, error)
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package pdf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"evening_detective/internal/models"
|
||||
"strings"
|
||||
|
||||
"github.com/signintech/gopdf"
|
||||
"github.com/skip2/go-qrcode"
|
||||
)
|
||||
|
||||
//go:embed JetBrainsMono-Medium.ttf
|
||||
var f embed.FS
|
||||
|
||||
type service struct{}
|
||||
|
||||
func NewPDFGenerator() IPDFGenerator {
|
||||
return &service{}
|
||||
}
|
||||
|
||||
func (s *service) CreateTeamsPDF(teams []*models.Team) ([]byte, error) {
|
||||
pdf := &gopdf.GoPdf{}
|
||||
pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4}) // W: 595, H: 842
|
||||
file, err := f.Open("JetBrainsMono-Medium.ttf")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := pdf.AddTTFFontByReader("main", file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := pdf.SetFont("main", "", 10); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
countOnPage := 9
|
||||
padding := 17.
|
||||
xDelta := 187.
|
||||
yDelta := 260.
|
||||
for i, team := range teams {
|
||||
if i%countOnPage == 0 {
|
||||
pdf.AddPage()
|
||||
pdf.SetPage(1 + i/countOnPage)
|
||||
}
|
||||
y := (padding + 15) + yDelta*float64(i%countOnPage/3)
|
||||
x := padding + xDelta*float64(i%3)
|
||||
|
||||
if err := printTextCenter(pdf, "Подключите Wi-Fi", xDelta-6, x+3, y); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := printTextCenter(pdf, "Имя: evening_detective", xDelta-6, x+3, 15+y); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := printTextCenter(pdf, "Пароль: 12345678", xDelta-6, x+3, 30+y); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := printQR(pdf, team.Link, x+21, 65+y); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := printTextCenter(pdf, "Войдите в приложение по qr", xDelta-6, x+3, 55+y); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := printTextCenter(pdf, team.Name, xDelta-6, x+3, 55+y+150); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
buffer := &bytes.Buffer{}
|
||||
_, err = pdf.WriteTo(buffer)
|
||||
return buffer.Bytes(), err
|
||||
}
|
||||
|
||||
func printQR(pdf *gopdf.GoPdf, url string, x, y float64) error {
|
||||
png, err := qrcode.Encode(url, qrcode.Medium, 256)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imgHolder, err := gopdf.ImageHolderByBytes(png)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = pdf.ImageByHolder(imgHolder, x, y, &gopdf.Rect{W: 145, H: 145})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printTextCenter(pdf *gopdf.GoPdf, text string, pageWidth, x, y float64) error {
|
||||
lines := splitTextByWords(pdf, text, pageWidth)
|
||||
for i, line := range lines {
|
||||
lineWidth, err := pdf.MeasureTextWidth(line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
xLine := x + (pageWidth-lineWidth)/2
|
||||
yLine := y + 15*float64(i)
|
||||
pdf.SetXY(xLine, yLine)
|
||||
|
||||
pdf.Cell(nil, line)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func splitTextByWords(pdf *gopdf.GoPdf, text string, maxWidth float64) []string {
|
||||
words := strings.Fields(text)
|
||||
var lines []string
|
||||
var currentLine string
|
||||
|
||||
for _, word := range words {
|
||||
testLine := currentLine
|
||||
if testLine != "" {
|
||||
testLine += " " + word
|
||||
} else {
|
||||
testLine = word
|
||||
}
|
||||
|
||||
width, _ := pdf.MeasureTextWidth(testLine)
|
||||
|
||||
if width <= maxWidth {
|
||||
currentLine = testLine
|
||||
} else {
|
||||
if currentLine != "" {
|
||||
lines = append(lines, currentLine)
|
||||
}
|
||||
currentLine = word
|
||||
}
|
||||
}
|
||||
|
||||
if currentLine != "" {
|
||||
lines = append(lines, currentLine)
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"evening_detective/internal/models"
|
||||
"evening_detective/internal/modules/password"
|
||||
"evening_detective/internal/services/db"
|
||||
"evening_detective/internal/services/link"
|
||||
"evening_detective/internal/services/pdf"
|
||||
"evening_detective/internal/services/story_service"
|
||||
"evening_detective/proto"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type Services struct {
|
||||
dbService db.IDBService
|
||||
storyService *story_service.StoryService
|
||||
linkService link.ILinkService
|
||||
passwordGenerator password.IPasswordGenerator
|
||||
pdfGenerator pdf.IPDFGenerator
|
||||
}
|
||||
|
||||
func NewServices(
|
||||
dbService db.IDBService,
|
||||
storyService *story_service.StoryService,
|
||||
linkService link.ILinkService,
|
||||
passwordGenerator password.IPasswordGenerator,
|
||||
pdfGenerator pdf.IPDFGenerator,
|
||||
) *Services {
|
||||
return &Services{
|
||||
dbService: dbService,
|
||||
storyService: storyService,
|
||||
linkService: linkService,
|
||||
passwordGenerator: passwordGenerator,
|
||||
pdfGenerator: pdfGenerator,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Services) GiveApplications(ctx context.Context, req *proto.GiveApplicationsReq) (*proto.GiveApplicationsRsp, error) {
|
||||
applications := mapProtoApplicationsToApplications(req.Applications)
|
||||
if err := s.dbService.GiveApplications(ctx, req.TeamId, applications); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
return &proto.GiveApplicationsRsp{}, nil
|
||||
}
|
||||
|
||||
func (s *Services) GetGame(ctx context.Context, _ *proto.GetGameReq) (*proto.GetGameRsp, error) {
|
||||
game, err := s.dbService.GetGame(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
return &proto.GetGameRsp{
|
||||
State: game.State,
|
||||
StartAt: game.StartTime,
|
||||
EndAt: game.EndTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Services) GameStart(ctx context.Context, _ *proto.GameStartReq) (*proto.GameStartRsp, error) {
|
||||
if err := s.dbService.UpdateGameState(ctx, "RUN"); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
return &proto.GameStartRsp{}, nil
|
||||
}
|
||||
|
||||
func (s *Services) GameStop(ctx context.Context, req *proto.GameStopReq) (*proto.GameStopRsp, error) {
|
||||
if err := s.dbService.UpdateGameState(ctx, "STOP"); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
return &proto.GameStopRsp{}, nil
|
||||
}
|
||||
|
||||
func (s *Services) AddAction(ctx context.Context, req *proto.AddActionReq) (*proto.AddActionRsp, error) {
|
||||
team, err := s.getTeam(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
place := s.storyService.GetPlace(req.Place)
|
||||
actions := []*models.Action{
|
||||
{
|
||||
Place: place.Code,
|
||||
TeamID: team.ID,
|
||||
Applications: mapStoryApplicationsToApplications(place.Applications),
|
||||
},
|
||||
}
|
||||
if err := s.dbService.AddActions(ctx, team.ID, actions); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
currentApplications, err := s.dbService.GetApplications(ctx, team.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newApplications := make([]*models.Application, 0, len(actions))
|
||||
for _, action := range actions {
|
||||
for _, actionApplication := range action.Applications {
|
||||
f := false
|
||||
for _, currentApplication := range currentApplications {
|
||||
if currentApplication.Name == actionApplication.Name {
|
||||
f = true
|
||||
}
|
||||
}
|
||||
if !f {
|
||||
newApplications = append(newApplications, actionApplication)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.dbService.AddApplications(ctx, team.ID, newApplications); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
addLog(team, "add action", actions)
|
||||
return &proto.AddActionRsp{}, nil
|
||||
}
|
||||
|
||||
func (s *Services) GetTeam(ctx context.Context, req *proto.GetTeamReq) (*proto.GetTeamRsp, error) {
|
||||
team, err := s.getTeam(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
actions, err := s.dbService.GetActions(ctx, team.ID)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
actionsCodes := make([]string, 0, len(actions))
|
||||
for _, action := range actions {
|
||||
actionsCodes = append(actionsCodes, action.Place)
|
||||
}
|
||||
|
||||
res := make([]*proto.Action, 0, len(actions))
|
||||
for i, place := range s.storyService.GetPlaces(actionsCodes) {
|
||||
newAction := mapPlaceToProtoAction(place)
|
||||
newAction.Id = actions[i].ID
|
||||
newAction.Name = place.Name
|
||||
newAction.Text = place.Text
|
||||
newAction.Image = place.Image
|
||||
newAction.Applications = make([]*proto.Application, 0, len(place.Applications))
|
||||
for _, application := range place.Applications {
|
||||
newAction.Applications = append(newAction.Applications, mapStoryApplicationToProtoApplication(application))
|
||||
}
|
||||
newAction.Hidden = place.Hidden
|
||||
newAction.Doors = make([]*proto.Door, 0, len(place.Doors))
|
||||
for _, door := range place.Doors {
|
||||
newAction.Doors = append(newAction.Doors, mapStoryDoorToProtoDoor(door))
|
||||
}
|
||||
res = append(res, newAction)
|
||||
}
|
||||
return &proto.GetTeamRsp{
|
||||
Name: team.Name,
|
||||
Actions: res,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (s *Services) GetTeamsCSV(ctx context.Context, req *proto.GetTeamsCSVReq) (*proto.GetTeamsCSVRsp, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (s *Services) GetTeams(ctx context.Context, _ *proto.GetTeamsReq) (*proto.GetTeamsRsp, error) {
|
||||
teams, err := s.dbService.GetTeams(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
res := make([]*proto.TeamAdvanced, 0, len(teams))
|
||||
for _, team := range teams {
|
||||
newTeam := mapTeamsToTeamAdvanced(team)
|
||||
actions, err := s.dbService.GetActions(ctx, team.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTeam.Url = s.linkService.GetTeamClientLink(team.Name, team.Password)
|
||||
newTeam.SpendTime = int64(len(actions))
|
||||
currentApplications, err := s.dbService.GetApplicationsByState(ctx, team.ID, "NEW")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTeam.Applications = mapApplicationsToProtoApplications(currentApplications)
|
||||
res = append(res, newTeam)
|
||||
}
|
||||
return &proto.GetTeamsRsp{Teams: res}, err
|
||||
}
|
||||
|
||||
func (s *Services) AddTeams(ctx context.Context, req *proto.AddTeamsReq) (*proto.AddTeamsRsp, error) {
|
||||
inTeams := make([]*models.Team, 0, len(req.Teams))
|
||||
for _, team := range req.Teams {
|
||||
t := mapProtoTeamsToTeam(team)
|
||||
t.Password = s.passwordGenerator.GeneratePassword(8)
|
||||
inTeams = append(inTeams, t)
|
||||
}
|
||||
teams, err := s.dbService.AddTeams(ctx, inTeams)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
res := make([]*proto.TeamFull, 0, len(teams))
|
||||
for _, team := range teams {
|
||||
res = append(res, mapTeamsToTeamFull(team))
|
||||
}
|
||||
return &proto.AddTeamsRsp{Teams: res}, nil
|
||||
}
|
||||
|
||||
func (s *Services) DownloadTeamsQrCodesFile(ctx context.Context, req *proto.DownloadTeamsQrCodesFileReq) (*proto.DownloadTeamsQrCodesFileRsp, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||
defer cancel()
|
||||
teams, err := s.dbService.GetTeams(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, team := range teams {
|
||||
team.Link = s.linkService.GetTeamClientLink(team.Name, team.Password)
|
||||
}
|
||||
b, err := s.pdfGenerator.CreateTeamsPDF(teams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &proto.DownloadTeamsQrCodesFileRsp{Result: b}, nil
|
||||
}
|
||||
|
||||
func (s *Services) UpdateNode(ctx context.Context, req *proto.UpdateNodeReq) (*proto.UpdateNodeRsp, error) {
|
||||
applications := make([]*story_service.GraphApplication, 0, len(req.Node.Applications))
|
||||
for _, application := range req.Node.Applications {
|
||||
applications = append(
|
||||
applications,
|
||||
&story_service.GraphApplication{
|
||||
Name: application.Name,
|
||||
},
|
||||
)
|
||||
}
|
||||
doors := make([]*story_service.GraphDoor, 0, len(req.Node.Doors))
|
||||
for _, door := range req.Node.Doors {
|
||||
doors = append(
|
||||
doors,
|
||||
&story_service.GraphDoor{
|
||||
Code: door.Code,
|
||||
Name: door.Name,
|
||||
Show: door.Show,
|
||||
},
|
||||
)
|
||||
}
|
||||
node := &story_service.GraphNode{
|
||||
Code: req.Node.Code,
|
||||
Name: req.Node.Name,
|
||||
Text: req.Node.Text,
|
||||
Image: req.Node.Image,
|
||||
Applications: applications,
|
||||
Hidden: req.Node.Hidden,
|
||||
Doors: doors,
|
||||
}
|
||||
if err := s.storyService.UpdatePlace(ctx, req.Code, node); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &proto.UpdateNodeRsp{}, nil
|
||||
}
|
||||
|
||||
func (s *Services) GetGraph(ctx context.Context, req *proto.GetGraphReq) (*proto.GetGraphRsp, error) {
|
||||
graph := s.storyService.GetGraph(ctx)
|
||||
nodes := make([]*proto.GraphNode, 0, len(graph.Nodes))
|
||||
for _, node := range graph.Nodes {
|
||||
applications := make([]*proto.GraphApplication, 0, len(node.Applications))
|
||||
for _, application := range node.Applications {
|
||||
applications = append(
|
||||
applications,
|
||||
&proto.GraphApplication{
|
||||
Name: application.Name,
|
||||
},
|
||||
)
|
||||
}
|
||||
doors := make([]*proto.GraphDoor, 0, len(node.Doors))
|
||||
for _, door := range node.Doors {
|
||||
doors = append(
|
||||
doors,
|
||||
&proto.GraphDoor{
|
||||
Code: door.Code,
|
||||
Name: door.Name,
|
||||
Show: door.Show,
|
||||
},
|
||||
)
|
||||
}
|
||||
nodes = append(nodes, &proto.GraphNode{
|
||||
Code: node.Code,
|
||||
Name: node.Name,
|
||||
Text: node.Text,
|
||||
Image: node.Image,
|
||||
Applications: applications,
|
||||
Hidden: node.Hidden,
|
||||
Doors: doors,
|
||||
})
|
||||
}
|
||||
edges := make([]*proto.GetGraphRsp_Edge, 0, len(graph.Edges))
|
||||
for _, edge := range graph.Edges {
|
||||
edges = append(edges, &proto.GetGraphRsp_Edge{
|
||||
From: edge.From,
|
||||
To: edge.To,
|
||||
Arrows: "to",
|
||||
Type: edge.Type,
|
||||
})
|
||||
}
|
||||
return &proto.GetGraphRsp{
|
||||
Nodes: nodes,
|
||||
Edges: edges,
|
||||
CountNodes: int32(len(nodes)),
|
||||
CountEdges: int32(len(edges)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Services) getTeam(ctx context.Context) (*models.Team, error) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.Unauthenticated, "error creds")
|
||||
}
|
||||
|
||||
teamIdArr, ok := md["team-id"]
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.Unauthenticated, "error creds")
|
||||
}
|
||||
teamId, err := base64.StdEncoding.DecodeString(teamIdArr[0])
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Unauthenticated, "error creds")
|
||||
}
|
||||
|
||||
passwordArr, ok := md["password"]
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.Unauthenticated, "error creds")
|
||||
}
|
||||
password := passwordArr[0]
|
||||
|
||||
team, err := s.dbService.GetTeam(ctx, teamId, password)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Unauthenticated, err.Error())
|
||||
}
|
||||
return team, nil
|
||||
}
|
||||
|
||||
func addLog(team *models.Team, action string, v any) {
|
||||
vJson, err := json.Marshal(v)
|
||||
if err == nil {
|
||||
fmt.Printf("Team %s: %s %s\n", team.Name, action, string(vJson))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package story_service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"evening_detective/internal/services/story_service/models"
|
||||
)
|
||||
|
||||
type IStoryStorage interface {
|
||||
Load(ctx context.Context) (*models.Story, error)
|
||||
Save(ctx context.Context, story *models.Story) error
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package story_service
|
||||
|
||||
type Graph struct {
|
||||
Nodes []*GraphNode
|
||||
Edges []*GraphEdge
|
||||
}
|
||||
|
||||
type GraphNode struct {
|
||||
Code string
|
||||
Name string
|
||||
Text string
|
||||
Image string
|
||||
Applications []*GraphApplication
|
||||
Hidden bool
|
||||
Doors []*GraphDoor
|
||||
}
|
||||
|
||||
type GraphEdge struct {
|
||||
From string
|
||||
To string
|
||||
Type string
|
||||
}
|
||||
|
||||
type GraphApplication struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type GraphDoor struct {
|
||||
Code string
|
||||
Name string
|
||||
Show bool
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package models
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Application struct {
|
||||
Name string `json:"name"`
|
||||
Number string `json:"-"`
|
||||
}
|
||||
|
||||
func NewApplication(
|
||||
name string,
|
||||
opts ...ApplicationOpt,
|
||||
) *Application {
|
||||
application := &Application{
|
||||
Name: name,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(application)
|
||||
}
|
||||
return application
|
||||
}
|
||||
|
||||
type ApplicationOpt func(application *Application) error
|
||||
|
||||
func WithApplicationNumber(number int) ApplicationOpt {
|
||||
return func(application *Application) error {
|
||||
application.SetNumber(number)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Application) SetNumber(number int) {
|
||||
a.Number = fmt.Sprintf("%d", number)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package models
|
||||
|
||||
type Door struct {
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Show bool `json:"show"`
|
||||
}
|
||||
|
||||
func NewDoor(
|
||||
code string,
|
||||
name string,
|
||||
opts ...DoorOpt,
|
||||
) *Door {
|
||||
door := &Door{
|
||||
Code: code,
|
||||
Name: name,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(door)
|
||||
}
|
||||
return door
|
||||
}
|
||||
|
||||
type DoorOpt func(door *Door) error
|
||||
|
||||
func WithDoorShow(show bool) DoorOpt {
|
||||
return func(door *Door) error {
|
||||
door.Show = show
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package models
|
||||
|
||||
type Place struct {
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Text string `json:"text"`
|
||||
Image string `json:"image"`
|
||||
Applications []*Application `json:"applications,omitempty"`
|
||||
Hidden bool `json:"hidden"`
|
||||
Doors []*Door `json:"doors"`
|
||||
}
|
||||
|
||||
func NewPlace(
|
||||
code string,
|
||||
name string,
|
||||
text string,
|
||||
opts ...PlaceOpt,
|
||||
) *Place {
|
||||
p := &Place{
|
||||
Code: code,
|
||||
Name: name,
|
||||
Text: text,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(p)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func NewNotFoundPlace(code string) *Place {
|
||||
return NewPlace(code, "Не найдено", "Такой точки не существует.")
|
||||
}
|
||||
|
||||
func NewClientErrorPlace(code string) *Place {
|
||||
return NewPlace(code, "Не найдено", "Детективы, внимательно читайте правила.")
|
||||
}
|
||||
|
||||
type PlaceOpt func(place *Place) error
|
||||
|
||||
func WithPlaceImage(image string) PlaceOpt {
|
||||
return func(place *Place) error {
|
||||
place.Image = image
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithPlaceApplication(applications ...*Application) PlaceOpt {
|
||||
return func(place *Place) error {
|
||||
for _, application := range applications {
|
||||
place.Applications = append(place.Applications, application)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithPlaceHidden(hidden bool) PlaceOpt {
|
||||
return func(place *Place) error {
|
||||
place.Hidden = hidden
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithPlaceDoors(doors ...*Door) PlaceOpt {
|
||||
return func(place *Place) error {
|
||||
for _, door := range doors {
|
||||
place.Doors = append(place.Doors, door)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package models
|
||||
|
||||
type Story struct {
|
||||
Places []*Place `json:"places"`
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
package story_service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"evening_detective/internal/modules/cleaner"
|
||||
"evening_detective/internal/modules/formatter"
|
||||
"evening_detective/internal/services/link"
|
||||
"evening_detective/internal/services/story_service/models"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type StoryService struct {
|
||||
cleaner cleaner.ICleaner
|
||||
formatter formatter.IFormatter
|
||||
story *models.Story
|
||||
storyStorage IStoryStorage
|
||||
linkService link.ILinkService
|
||||
}
|
||||
|
||||
func NewStoryService(
|
||||
cleaner cleaner.ICleaner,
|
||||
formatter formatter.IFormatter,
|
||||
storyStorage IStoryStorage,
|
||||
linkService link.ILinkService,
|
||||
) (*StoryService, error) {
|
||||
s := &StoryService{
|
||||
cleaner: cleaner,
|
||||
formatter: formatter,
|
||||
storyStorage: storyStorage,
|
||||
linkService: linkService,
|
||||
}
|
||||
story, err := s.storyStorage.Load(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.story = story
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *StoryService) Update(ctx context.Context) error {
|
||||
if err := s.storyStorage.Save(ctx, s.story); err != nil {
|
||||
return err
|
||||
}
|
||||
story, err := s.storyStorage.Load(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.story = story
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StoryService) GetPlace(code string) *models.Place {
|
||||
if strings.HasPrefix(code, "[") || strings.HasSuffix(code, "]") {
|
||||
return models.NewClientErrorPlace(code)
|
||||
}
|
||||
clearCode := s.cleaner.ClearCode(code)
|
||||
for _, place := range s.story.Places {
|
||||
if s.cleaner.ClearCode(place.Code) == clearCode {
|
||||
applications := make([]*models.Application, 0, len(place.Applications))
|
||||
for _, application := range place.Applications {
|
||||
applications = append(
|
||||
applications,
|
||||
models.NewApplication(
|
||||
s.cleaner.ClearText(application.Name),
|
||||
),
|
||||
)
|
||||
}
|
||||
doors := make([]*models.Door, 0, len(place.Doors))
|
||||
for _, door := range place.Doors {
|
||||
doors = append(
|
||||
doors,
|
||||
models.NewDoor(
|
||||
door.Code,
|
||||
door.Name,
|
||||
models.WithDoorShow(door.Show),
|
||||
),
|
||||
)
|
||||
}
|
||||
return models.NewPlace(
|
||||
place.Code,
|
||||
place.Name,
|
||||
s.cleaner.ClearText(place.Text),
|
||||
models.WithPlaceImage(s.linkService.GetImageLink(place.Image)),
|
||||
models.WithPlaceApplication(applications...),
|
||||
models.WithPlaceHidden(place.Hidden),
|
||||
models.WithPlaceDoors(doors...),
|
||||
)
|
||||
}
|
||||
}
|
||||
return models.NewNotFoundPlace(code)
|
||||
}
|
||||
|
||||
func (s *StoryService) GetPlaces(codes []string) []*models.Place {
|
||||
places := make([]*models.Place, 0, 100)
|
||||
mOpen := map[string]any{}
|
||||
mDeleted := map[string]any{}
|
||||
applicationNumber := 1
|
||||
applicationsMap := make(map[string]interface{}, 10)
|
||||
for i, code := range codes {
|
||||
place := s.GetPlace(code)
|
||||
for i, application := range place.Applications {
|
||||
if _, ok := applicationsMap[application.Name]; ok {
|
||||
place.Applications = append(place.Applications[:i], place.Applications[i+1:]...)
|
||||
if len(place.Applications) == 0 {
|
||||
place.Applications = nil
|
||||
}
|
||||
}
|
||||
applicationsMap[application.Name] = struct{}{}
|
||||
application.SetNumber(applicationNumber)
|
||||
applicationNumber++
|
||||
}
|
||||
if _, ok := mOpen[place.Code]; place.Hidden && !ok {
|
||||
place = models.NewNotFoundPlace(place.Code)
|
||||
}
|
||||
places = append(places, place)
|
||||
for j, door := range places[i].Doors {
|
||||
if _, ok := mDeleted[door.Code]; ok {
|
||||
places[i].Doors[j].Show = false
|
||||
continue
|
||||
}
|
||||
mOpen[door.Code] = struct{}{}
|
||||
}
|
||||
if i > 0 {
|
||||
for j, door := range places[i-1].Doors {
|
||||
if door.Code != place.Code && door.Show {
|
||||
places[i-1].Doors[j].Show = false
|
||||
delete(mOpen, door.Code)
|
||||
mDeleted[door.Code] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return places
|
||||
}
|
||||
|
||||
func (s *StoryService) UpdatePlace(ctx context.Context, code string, node *GraphNode) error {
|
||||
if code != "" && node.Code == "" {
|
||||
return s.deletePlace(ctx, code)
|
||||
}
|
||||
if code == "" && node.Code != "" {
|
||||
return s.addPlace(ctx, node)
|
||||
}
|
||||
if code == "" || node.Code == "" {
|
||||
return nil
|
||||
}
|
||||
return s.updatePlace(ctx, code, node)
|
||||
}
|
||||
|
||||
func (s *StoryService) deletePlace(ctx context.Context, code string) error {
|
||||
for i := range s.story.Places {
|
||||
if s.story.Places[i].Code == code {
|
||||
s.story.Places = append(s.story.Places[:i], s.story.Places[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
return s.Update(ctx)
|
||||
}
|
||||
|
||||
func (s *StoryService) addPlace(ctx context.Context, node *GraphNode) error {
|
||||
s.story.Places = append(
|
||||
s.story.Places,
|
||||
models.NewPlace(
|
||||
node.Code,
|
||||
node.Name,
|
||||
s.formatter.FormatText(node.Text),
|
||||
models.WithPlaceApplication(s.getApplications(node)...),
|
||||
),
|
||||
)
|
||||
return s.Update(ctx)
|
||||
}
|
||||
|
||||
func (s *StoryService) updatePlace(ctx context.Context, code string, node *GraphNode) error {
|
||||
for i := range s.story.Places {
|
||||
if s.story.Places[i].Code == code {
|
||||
s.story.Places[i] = models.NewPlace(
|
||||
node.Code,
|
||||
s.formatter.FormatString(node.Name),
|
||||
s.formatter.FormatText(node.Text),
|
||||
models.WithPlaceImage(node.Image),
|
||||
models.WithPlaceApplication(s.getApplications(node)...),
|
||||
models.WithPlaceHidden(node.Hidden),
|
||||
models.WithPlaceDoors(s.getDoors(node)...),
|
||||
)
|
||||
return s.Update(ctx)
|
||||
}
|
||||
}
|
||||
for i := range s.story.Places {
|
||||
if s.story.Places[i].Code == node.Code {
|
||||
s.story.Places[i] = models.NewPlace(
|
||||
code,
|
||||
s.formatter.FormatString(node.Name),
|
||||
s.formatter.FormatText(node.Text),
|
||||
models.WithPlaceImage(node.Image),
|
||||
models.WithPlaceApplication(s.getApplications(node)...),
|
||||
models.WithPlaceHidden(node.Hidden),
|
||||
models.WithPlaceDoors(s.getDoors(node)...),
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
return s.Update(ctx)
|
||||
}
|
||||
|
||||
func (s *StoryService) getApplications(node *GraphNode) []*models.Application {
|
||||
nodeApplications := make([]*models.Application, 0, len(node.Applications))
|
||||
for _, application := range node.Applications {
|
||||
nodeApplications = append(
|
||||
nodeApplications,
|
||||
&models.Application{
|
||||
Name: s.formatter.FormatString(application.Name),
|
||||
},
|
||||
)
|
||||
}
|
||||
return nodeApplications
|
||||
}
|
||||
|
||||
func (s *StoryService) getDoors(node *GraphNode) []*models.Door {
|
||||
nodeDoors := make([]*models.Door, 0, len(node.Doors))
|
||||
for _, door := range node.Doors {
|
||||
nodeDoors = append(
|
||||
nodeDoors,
|
||||
&models.Door{
|
||||
Code: door.Code,
|
||||
Name: s.formatter.FormatString(door.Name),
|
||||
Show: door.Show,
|
||||
},
|
||||
)
|
||||
}
|
||||
return nodeDoors
|
||||
}
|
||||
|
||||
func (s *StoryService) GetGraph(ctx context.Context) *Graph {
|
||||
m := make(map[string]string, len(s.story.Places))
|
||||
nodes := make([]*GraphNode, 0, len(s.story.Places))
|
||||
for _, place := range s.story.Places {
|
||||
m[s.cleaner.ClearCode(place.Code)] = place.Code
|
||||
applications := make([]*GraphApplication, 0, len(place.Applications))
|
||||
for _, application := range place.Applications {
|
||||
applications = append(
|
||||
applications,
|
||||
&GraphApplication{
|
||||
Name: application.Name,
|
||||
},
|
||||
)
|
||||
}
|
||||
doors := make([]*GraphDoor, 0, len(place.Doors))
|
||||
for _, door := range place.Doors {
|
||||
doors = append(
|
||||
doors,
|
||||
&GraphDoor{
|
||||
Code: door.Code,
|
||||
Name: door.Name,
|
||||
Show: door.Show,
|
||||
},
|
||||
)
|
||||
}
|
||||
nodes = append(
|
||||
nodes, &GraphNode{
|
||||
Code: place.Code,
|
||||
Name: place.Name,
|
||||
Text: place.Text,
|
||||
Image: place.Image,
|
||||
Applications: applications,
|
||||
Hidden: place.Hidden,
|
||||
Doors: doors,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
edges := make([]*GraphEdge, 0, len(s.story.Places)*3)
|
||||
for _, place := range s.story.Places {
|
||||
placeLinks := s.findPlaceLinksInText(place.Text)
|
||||
for _, placeLink := range placeLinks {
|
||||
edges = append(
|
||||
edges,
|
||||
&GraphEdge{
|
||||
From: m[s.cleaner.ClearCode(place.Code)],
|
||||
To: m[s.cleaner.ClearCode(placeLink)],
|
||||
Type: "node",
|
||||
},
|
||||
)
|
||||
}
|
||||
for _, application := range place.Applications {
|
||||
placeLinks := s.findPlaceLinksInText(application.Name)
|
||||
for _, placeLink := range placeLinks {
|
||||
edges = append(
|
||||
edges,
|
||||
&GraphEdge{
|
||||
From: m[s.cleaner.ClearCode(place.Code)],
|
||||
To: m[s.cleaner.ClearCode(placeLink)],
|
||||
Type: "application",
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
for _, door := range place.Doors {
|
||||
edges = append(
|
||||
edges,
|
||||
&GraphEdge{
|
||||
From: m[s.cleaner.ClearCode(place.Code)],
|
||||
To: m[s.cleaner.ClearCode(door.Code)],
|
||||
Type: "door",
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return &Graph{
|
||||
Nodes: nodes,
|
||||
Edges: edges,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StoryService) findPlaceLinksInText(text string) []string {
|
||||
re := regexp.MustCompile(`\(\[[a-zA-Zа-яА-Я\d-]+\]\)`)
|
||||
return re.FindAllString(text, -1)
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
package story_service_test
|
||||
|
||||
import (
|
||||
"evening_detective/internal/modules/cleaner"
|
||||
"evening_detective/internal/modules/formatter"
|
||||
"evening_detective/internal/services/link"
|
||||
"evening_detective/internal/services/story_service"
|
||||
"evening_detective/internal/services/story_service/models"
|
||||
"evening_detective/internal/services/story_storage"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStoryService_GetPlace(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
story *models.Story
|
||||
code string
|
||||
want *models.Place
|
||||
}{
|
||||
{
|
||||
name: "не корректный ввода",
|
||||
story: &models.Story{},
|
||||
code: "[Ы]",
|
||||
want: models.NewClientErrorPlace("[Ы]"),
|
||||
},
|
||||
{
|
||||
name: "точка не найдена",
|
||||
story: &models.Story{},
|
||||
code: "Ы",
|
||||
want: models.NewNotFoundPlace("Ы"),
|
||||
},
|
||||
{
|
||||
name: "получение точки",
|
||||
story: &models.Story{
|
||||
Places: []*models.Place{
|
||||
models.NewPlace("Ы", "Название", "Текст"),
|
||||
},
|
||||
},
|
||||
code: "Ы",
|
||||
want: models.NewPlace("Ы", "Название", "Текст"),
|
||||
},
|
||||
{
|
||||
name: "получение скрытой точки",
|
||||
story: &models.Story{
|
||||
Places: []*models.Place{
|
||||
models.NewPlace(
|
||||
"Ы",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
},
|
||||
},
|
||||
code: "Ы",
|
||||
want: models.NewPlace(
|
||||
"Ы",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "получение точки с приложением",
|
||||
story: &models.Story{
|
||||
Places: []*models.Place{
|
||||
models.NewPlace(
|
||||
"Ы",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceApplication(
|
||||
models.NewApplication("Приложение"),
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
code: "Ы",
|
||||
want: models.NewPlace(
|
||||
"Ы",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceApplication(
|
||||
models.NewApplication("Приложение"),
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "получение точки с проходом",
|
||||
story: &models.Story{
|
||||
Places: []*models.Place{
|
||||
models.NewPlace(
|
||||
"Ы",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceDoors(
|
||||
models.NewDoor("Й", "Приложение"),
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
code: "Ы",
|
||||
want: models.NewPlace(
|
||||
"Ы",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceDoors(
|
||||
models.NewDoor("Й", "Приложение"),
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "получение точки с диалогом",
|
||||
story: &models.Story{
|
||||
Places: []*models.Place{
|
||||
models.NewPlace(
|
||||
"Ы",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceDoors(
|
||||
models.NewDoor(
|
||||
"Й",
|
||||
"Приложение",
|
||||
models.WithDoorShow(true),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
code: "Ы",
|
||||
want: models.NewPlace(
|
||||
"Ы",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceDoors(
|
||||
models.NewDoor(
|
||||
"Й",
|
||||
"Приложение",
|
||||
models.WithDoorShow(true),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s, err := story_service.NewStoryService(
|
||||
cleaner.NewCleaner(),
|
||||
formatter.NewFormatter(),
|
||||
story_storage.NewVarStoryStorage(tt.story),
|
||||
link.NewLinkService("http://localhost:8120"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("could not construct receiver type: %v", err)
|
||||
}
|
||||
got := s.GetPlace(tt.code)
|
||||
assert.Equal(t, got, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoryService_GetPlaces(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
story *models.Story
|
||||
codes []string
|
||||
want []*models.Place
|
||||
}{
|
||||
{
|
||||
name: "Можно сходить в открытую точку",
|
||||
story: &models.Story{
|
||||
Places: []*models.Place{
|
||||
models.NewPlace("Ы", "Название", "Текст"),
|
||||
},
|
||||
},
|
||||
codes: []string{"Ы"},
|
||||
want: []*models.Place{
|
||||
models.NewPlace("Ы", "Название", "Текст"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Нельзя открыть скрытую точку",
|
||||
story: &models.Story{
|
||||
Places: []*models.Place{
|
||||
models.NewPlace(
|
||||
"Ы",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
},
|
||||
},
|
||||
codes: []string{"Ы"},
|
||||
want: []*models.Place{
|
||||
models.NewNotFoundPlace("Ы"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Открываем скрытую точку",
|
||||
story: &models.Story{
|
||||
Places: []*models.Place{
|
||||
models.NewPlace(
|
||||
"Ы-1",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceDoors(
|
||||
models.NewDoor("Ы-2", "Название"),
|
||||
models.NewDoor("Ы-3", "Название"),
|
||||
),
|
||||
),
|
||||
models.NewPlace(
|
||||
"Ы-2",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
models.NewPlace(
|
||||
"Ы-3",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
},
|
||||
},
|
||||
codes: []string{"Ы-1", "Ы-2", "Ы-3"},
|
||||
want: []*models.Place{
|
||||
models.NewPlace(
|
||||
"Ы-1",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceDoors(
|
||||
models.NewDoor("Ы-2", "Название"),
|
||||
models.NewDoor("Ы-3", "Название"),
|
||||
),
|
||||
),
|
||||
models.NewPlace(
|
||||
"Ы-2",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
models.NewPlace(
|
||||
"Ы-3",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Открываем скрытую точку диалога",
|
||||
story: &models.Story{
|
||||
Places: []*models.Place{
|
||||
models.NewPlace(
|
||||
"Ы-1",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceDoors(
|
||||
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
|
||||
),
|
||||
),
|
||||
models.NewPlace(
|
||||
"Ы-2",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
models.NewPlace(
|
||||
"Ы-3",
|
||||
"Название",
|
||||
"Текст",
|
||||
),
|
||||
},
|
||||
},
|
||||
codes: []string{"Ы-1", "Ы-3", "Ы-2"},
|
||||
want: []*models.Place{
|
||||
models.NewPlace(
|
||||
"Ы-1",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceDoors(
|
||||
models.NewDoor("Ы-2", "Название", models.WithDoorShow(false)),
|
||||
),
|
||||
),
|
||||
models.NewPlace(
|
||||
"Ы-3",
|
||||
"Название",
|
||||
"Текст",
|
||||
),
|
||||
models.NewNotFoundPlace("Ы-2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Открываем скрытую точку диалога",
|
||||
story: &models.Story{
|
||||
Places: []*models.Place{
|
||||
models.NewPlace(
|
||||
"Ы-1",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceDoors(
|
||||
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
|
||||
models.NewDoor("Ы-3", "Название", models.WithDoorShow(true)),
|
||||
),
|
||||
),
|
||||
models.NewPlace(
|
||||
"Ы-2",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
models.NewPlace(
|
||||
"Ы-3",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
},
|
||||
},
|
||||
codes: []string{"Ы-1", "Ы-2", "Ы-3"},
|
||||
want: []*models.Place{
|
||||
models.NewPlace(
|
||||
"Ы-1",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceDoors(
|
||||
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
|
||||
models.NewDoor("Ы-3", "Название", models.WithDoorShow(false)),
|
||||
),
|
||||
),
|
||||
models.NewPlace(
|
||||
"Ы-2",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
models.NewNotFoundPlace("Ы-3"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Открываем скрытую точку после диалога",
|
||||
story: &models.Story{
|
||||
Places: []*models.Place{
|
||||
models.NewPlace(
|
||||
"Ы-1",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceDoors(
|
||||
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
|
||||
models.NewDoor("Ы-3", "Название", models.WithDoorShow(true)),
|
||||
models.NewDoor("Ы-4", "Название"),
|
||||
),
|
||||
),
|
||||
models.NewPlace(
|
||||
"Ы-2",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
models.NewPlace(
|
||||
"Ы-3",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
models.NewPlace(
|
||||
"Ы-4",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
},
|
||||
},
|
||||
codes: []string{"Ы-1", "Ы-2", "Ы-3", "Ы-4"},
|
||||
want: []*models.Place{
|
||||
models.NewPlace(
|
||||
"Ы-1",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceDoors(
|
||||
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
|
||||
models.NewDoor("Ы-3", "Название", models.WithDoorShow(false)),
|
||||
models.NewDoor("Ы-4", "Название"),
|
||||
),
|
||||
),
|
||||
models.NewPlace(
|
||||
"Ы-2",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
models.NewNotFoundPlace("Ы-3"),
|
||||
models.NewPlace(
|
||||
"Ы-4",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Открытие второй раз точки не дает задать другой вопрос",
|
||||
story: &models.Story{
|
||||
Places: []*models.Place{
|
||||
models.NewPlace(
|
||||
"Ы-1",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceDoors(
|
||||
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
|
||||
models.NewDoor("Ы-3", "Название", models.WithDoorShow(true)),
|
||||
),
|
||||
),
|
||||
models.NewPlace(
|
||||
"Ы-2",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
models.NewPlace(
|
||||
"Ы-3",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
},
|
||||
},
|
||||
codes: []string{"Ы-1", "Ы-2", "Ы-3", "Ы-1", "Ы-3", "Ы-2"},
|
||||
want: []*models.Place{
|
||||
models.NewPlace(
|
||||
"Ы-1",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceDoors(
|
||||
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
|
||||
models.NewDoor("Ы-3", "Название", models.WithDoorShow(false)),
|
||||
),
|
||||
),
|
||||
models.NewPlace(
|
||||
"Ы-2",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceHidden(true),
|
||||
),
|
||||
models.NewNotFoundPlace("Ы-3"),
|
||||
models.NewPlace(
|
||||
"Ы-1",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceDoors(
|
||||
models.NewDoor("Ы-2", "Название", models.WithDoorShow(false)),
|
||||
models.NewDoor("Ы-3", "Название", models.WithDoorShow(false)),
|
||||
),
|
||||
),
|
||||
models.NewNotFoundPlace("Ы-3"),
|
||||
models.NewNotFoundPlace("Ы-2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Улики не повторяются",
|
||||
story: &models.Story{
|
||||
Places: []*models.Place{
|
||||
models.NewPlace(
|
||||
"Ы",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceApplication(
|
||||
models.NewApplication("Название"),
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
codes: []string{"Ы", "Ы"},
|
||||
want: []*models.Place{
|
||||
models.NewPlace(
|
||||
"Ы",
|
||||
"Название",
|
||||
"Текст",
|
||||
models.WithPlaceApplication(
|
||||
models.NewApplication(
|
||||
"Название",
|
||||
models.WithApplicationNumber(1),
|
||||
),
|
||||
),
|
||||
),
|
||||
models.NewPlace(
|
||||
"Ы",
|
||||
"Название",
|
||||
"Текст",
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s, err := story_service.NewStoryService(
|
||||
cleaner.NewCleaner(),
|
||||
formatter.NewFormatter(),
|
||||
story_storage.NewVarStoryStorage(tt.story),
|
||||
link.NewLinkService("http://localhost:8120"),
|
||||
)
|
||||
assert.Nil(t, err)
|
||||
|
||||
got := s.GetPlaces(tt.codes)
|
||||
assert.Equal(t, got, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package story_storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"evening_detective/internal/services/story_service"
|
||||
"evening_detective/internal/services/story_service/models"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type fileService struct {
|
||||
filepath string
|
||||
}
|
||||
|
||||
func NewFileStoryStorage(filepath string) story_service.IStoryStorage {
|
||||
return &fileService{
|
||||
filepath: filepath,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *fileService) Load(ctx context.Context) (*models.Story, error) {
|
||||
data, err := os.ReadFile(s.filepath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("story file %s not found", s.filepath)
|
||||
}
|
||||
log.Printf("Load story from: %s", s.filepath)
|
||||
story := &models.Story{}
|
||||
if err := json.Unmarshal(data, story); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("Found %d places", len(story.Places))
|
||||
return story, nil
|
||||
}
|
||||
|
||||
func (s *fileService) Save(ctx context.Context, story *models.Story) error {
|
||||
data, err := json.Marshal(story)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(s.filepath, data, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("save story to: %s", s.filepath)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package story_storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"evening_detective/internal/services/story_service/models"
|
||||
)
|
||||
|
||||
type IStoryStorage interface {
|
||||
Load(ctx context.Context) (*models.Story, error)
|
||||
Save(ctx context.Context, story *models.Story) error
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package story_storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"evening_detective/internal/services/story_service"
|
||||
"evening_detective/internal/services/story_service/models"
|
||||
)
|
||||
|
||||
type varService struct {
|
||||
story *models.Story
|
||||
}
|
||||
|
||||
func NewVarStoryStorage(story *models.Story) story_service.IStoryStorage {
|
||||
return &varService{
|
||||
story: story,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *varService) Load(ctx context.Context) (*models.Story, error) {
|
||||
return s.story, nil
|
||||
}
|
||||
|
||||
func (s *varService) Save(ctx context.Context, story *models.Story) error {
|
||||
s.story = story
|
||||
return nil
|
||||
}
|
||||
@@ -11,14 +11,14 @@
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/actions": {
|
||||
"post": {
|
||||
"operationId": "EveningDetective_AddAction",
|
||||
"/csv": {
|
||||
"get": {
|
||||
"operationId": "EveningDetective_GetTeamsCSV",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detectiveAddActionRsp"
|
||||
"$ref": "#/definitions/evening_detectiveGetTeamsCSVRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
@@ -33,14 +33,14 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/csv": {
|
||||
"/game": {
|
||||
"get": {
|
||||
"operationId": "EveningDetective_GetTeamsCSV",
|
||||
"operationId": "EveningDetective_GetGame",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detectiveGetTeamsCSVRsp"
|
||||
"$ref": "#/definitions/evening_detectiveGetGameRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
@@ -119,6 +119,60 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/graph": {
|
||||
"get": {
|
||||
"operationId": "EveningDetective_GetGraph",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detectiveGetGraphRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/runtimeError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"EveningDetective"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/graph/nodes": {
|
||||
"put": {
|
||||
"operationId": "EveningDetective_UpdateNode",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detectiveUpdateNodeRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/runtimeError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detectiveUpdateNodeReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"EveningDetective"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ping": {
|
||||
"get": {
|
||||
"operationId": "EveningDetective_Ping",
|
||||
@@ -141,14 +195,14 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/teams": {
|
||||
"/team": {
|
||||
"get": {
|
||||
"operationId": "EveningDetective_GetTeams",
|
||||
"operationId": "EveningDetective_GetTeam",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detectiveGetTeamsRsp"
|
||||
"$ref": "#/definitions/evening_detectiveGetTeamRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
@@ -161,14 +215,48 @@
|
||||
"tags": [
|
||||
"EveningDetective"
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"operationId": "EveningDetective_DeleteTeams",
|
||||
}
|
||||
},
|
||||
"/team/actions": {
|
||||
"post": {
|
||||
"operationId": "EveningDetective_AddAction",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detectiveDeleteTeamsRsp"
|
||||
"$ref": "#/definitions/evening_detectiveAddActionRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/runtimeError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detectiveAddActionReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"EveningDetective"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/teams": {
|
||||
"get": {
|
||||
"operationId": "EveningDetective_GetTeams",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detectiveGetTeamsRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
@@ -213,14 +301,14 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/teams/{id}": {
|
||||
"/teams/pdf": {
|
||||
"get": {
|
||||
"operationId": "EveningDetective_GetTeam",
|
||||
"operationId": "EveningDetective_DownloadTeamsQrCodesFile",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/evening_detectiveGetTeamRsp"
|
||||
"$ref": "#/definitions/evening_detectiveDownloadTeamsQrCodesFileRsp"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
@@ -230,15 +318,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"EveningDetective"
|
||||
]
|
||||
@@ -285,27 +364,70 @@
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"evening_detectiveAddActionRsp": {
|
||||
"GetGraphRspEdge": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"from": {
|
||||
"type": "string"
|
||||
},
|
||||
"to": {
|
||||
"type": "string"
|
||||
},
|
||||
"arrows": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detectiveAction": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
},
|
||||
"to": {
|
||||
"place": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"image": {
|
||||
"type": "string"
|
||||
},
|
||||
"applications": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/evening_detectiveApplication"
|
||||
}
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"doors": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/evening_detectiveDoor"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detectiveAddActionReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"place": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detectiveAddActionRsp": {
|
||||
"type": "object"
|
||||
},
|
||||
"evening_detectiveAddTeamsReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -331,13 +453,43 @@
|
||||
"evening_detectiveApplication": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "string"
|
||||
},
|
||||
"number": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detectiveDeleteTeamsRsp": {
|
||||
"type": "object"
|
||||
"evening_detectiveDoor": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"show": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detectiveDownloadTeamsQrCodesFileRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"result": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detectiveGameStartReq": {
|
||||
"type": "object"
|
||||
@@ -357,13 +509,55 @@
|
||||
"evening_detectiveGameStopRsp": {
|
||||
"type": "object"
|
||||
},
|
||||
"evening_detectiveGetGameRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"state": {
|
||||
"type": "string"
|
||||
},
|
||||
"startAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"endAt": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detectiveGetGraphRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"nodes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/evening_detectiveGraphNode"
|
||||
}
|
||||
},
|
||||
"edges": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/GetGraphRspEdge"
|
||||
}
|
||||
},
|
||||
"countNodes": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"countEdges": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detectiveGetTeamRsp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"actions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/evening_detectiveAddActionRsp"
|
||||
"$ref": "#/definitions/evening_detectiveAction"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -405,6 +599,60 @@
|
||||
"evening_detectiveGiveApplicationsRsp": {
|
||||
"type": "object"
|
||||
},
|
||||
"evening_detectiveGraphApplication": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detectiveGraphDoor": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"show": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detectiveGraphNode": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"image": {
|
||||
"type": "string"
|
||||
},
|
||||
"applications": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/evening_detectiveGraphApplication"
|
||||
}
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"doors": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/evening_detectiveGraphDoor"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detectivePingRsp": {
|
||||
"type": "object"
|
||||
},
|
||||
@@ -426,6 +674,12 @@
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"spendTime": {
|
||||
"type": "string",
|
||||
"format": "int64"
|
||||
@@ -453,6 +707,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detectiveUpdateNodeReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string"
|
||||
},
|
||||
"node": {
|
||||
"$ref": "#/definitions/evening_detectiveGraphNode"
|
||||
}
|
||||
}
|
||||
},
|
||||
"evening_detectiveUpdateNodeRsp": {
|
||||
"type": "object"
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.3.0
|
||||
// - protoc v5.26.1
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v6.32.1
|
||||
// source: main.proto
|
||||
|
||||
package proto
|
||||
@@ -15,20 +15,23 @@ import (
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
EveningDetective_Ping_FullMethodName = "/crabs.evening_detective.EveningDetective/Ping"
|
||||
EveningDetective_AddTeams_FullMethodName = "/crabs.evening_detective.EveningDetective/AddTeams"
|
||||
EveningDetective_GetTeams_FullMethodName = "/crabs.evening_detective.EveningDetective/GetTeams"
|
||||
EveningDetective_GetTeamsCSV_FullMethodName = "/crabs.evening_detective.EveningDetective/GetTeamsCSV"
|
||||
EveningDetective_GetTeam_FullMethodName = "/crabs.evening_detective.EveningDetective/GetTeam"
|
||||
EveningDetective_DeleteTeams_FullMethodName = "/crabs.evening_detective.EveningDetective/DeleteTeams"
|
||||
EveningDetective_AddAction_FullMethodName = "/crabs.evening_detective.EveningDetective/AddAction"
|
||||
EveningDetective_GameStart_FullMethodName = "/crabs.evening_detective.EveningDetective/GameStart"
|
||||
EveningDetective_GameStop_FullMethodName = "/crabs.evening_detective.EveningDetective/GameStop"
|
||||
EveningDetective_GiveApplications_FullMethodName = "/crabs.evening_detective.EveningDetective/GiveApplications"
|
||||
EveningDetective_Ping_FullMethodName = "/crabs.evening_detective.EveningDetective/Ping"
|
||||
EveningDetective_AddTeams_FullMethodName = "/crabs.evening_detective.EveningDetective/AddTeams"
|
||||
EveningDetective_GetTeams_FullMethodName = "/crabs.evening_detective.EveningDetective/GetTeams"
|
||||
EveningDetective_GetTeamsCSV_FullMethodName = "/crabs.evening_detective.EveningDetective/GetTeamsCSV"
|
||||
EveningDetective_GetTeam_FullMethodName = "/crabs.evening_detective.EveningDetective/GetTeam"
|
||||
EveningDetective_AddAction_FullMethodName = "/crabs.evening_detective.EveningDetective/AddAction"
|
||||
EveningDetective_GetGame_FullMethodName = "/crabs.evening_detective.EveningDetective/GetGame"
|
||||
EveningDetective_GameStart_FullMethodName = "/crabs.evening_detective.EveningDetective/GameStart"
|
||||
EveningDetective_GameStop_FullMethodName = "/crabs.evening_detective.EveningDetective/GameStop"
|
||||
EveningDetective_GiveApplications_FullMethodName = "/crabs.evening_detective.EveningDetective/GiveApplications"
|
||||
EveningDetective_DownloadTeamsQrCodesFile_FullMethodName = "/crabs.evening_detective.EveningDetective/DownloadTeamsQrCodesFile"
|
||||
EveningDetective_GetGraph_FullMethodName = "/crabs.evening_detective.EveningDetective/GetGraph"
|
||||
EveningDetective_UpdateNode_FullMethodName = "/crabs.evening_detective.EveningDetective/UpdateNode"
|
||||
)
|
||||
|
||||
// EveningDetectiveClient is the client API for EveningDetective service.
|
||||
@@ -40,11 +43,14 @@ type EveningDetectiveClient interface {
|
||||
GetTeams(ctx context.Context, in *GetTeamsReq, opts ...grpc.CallOption) (*GetTeamsRsp, error)
|
||||
GetTeamsCSV(ctx context.Context, in *GetTeamsCSVReq, opts ...grpc.CallOption) (*GetTeamsCSVRsp, error)
|
||||
GetTeam(ctx context.Context, in *GetTeamReq, opts ...grpc.CallOption) (*GetTeamRsp, error)
|
||||
DeleteTeams(ctx context.Context, in *DeleteTeamsReq, opts ...grpc.CallOption) (*DeleteTeamsRsp, error)
|
||||
AddAction(ctx context.Context, in *AddActionReq, opts ...grpc.CallOption) (*AddActionRsp, error)
|
||||
GetGame(ctx context.Context, in *GetGameReq, opts ...grpc.CallOption) (*GetGameRsp, error)
|
||||
GameStart(ctx context.Context, in *GameStartReq, opts ...grpc.CallOption) (*GameStartRsp, error)
|
||||
GameStop(ctx context.Context, in *GameStopReq, opts ...grpc.CallOption) (*GameStopRsp, error)
|
||||
GiveApplications(ctx context.Context, in *GiveApplicationsReq, opts ...grpc.CallOption) (*GiveApplicationsRsp, error)
|
||||
DownloadTeamsQrCodesFile(ctx context.Context, in *DownloadTeamsQrCodesFileReq, opts ...grpc.CallOption) (*DownloadTeamsQrCodesFileRsp, error)
|
||||
GetGraph(ctx context.Context, in *GetGraphReq, opts ...grpc.CallOption) (*GetGraphRsp, error)
|
||||
UpdateNode(ctx context.Context, in *UpdateNodeReq, opts ...grpc.CallOption) (*UpdateNodeRsp, error)
|
||||
}
|
||||
|
||||
type eveningDetectiveClient struct {
|
||||
@@ -56,8 +62,9 @@ func NewEveningDetectiveClient(cc grpc.ClientConnInterface) EveningDetectiveClie
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveClient) Ping(ctx context.Context, in *PingReq, opts ...grpc.CallOption) (*PingRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(PingRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_Ping_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_Ping_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -65,8 +72,9 @@ func (c *eveningDetectiveClient) Ping(ctx context.Context, in *PingReq, opts ...
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveClient) AddTeams(ctx context.Context, in *AddTeamsReq, opts ...grpc.CallOption) (*AddTeamsRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(AddTeamsRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_AddTeams_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_AddTeams_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -74,8 +82,9 @@ func (c *eveningDetectiveClient) AddTeams(ctx context.Context, in *AddTeamsReq,
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveClient) GetTeams(ctx context.Context, in *GetTeamsReq, opts ...grpc.CallOption) (*GetTeamsRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetTeamsRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_GetTeams_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_GetTeams_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -83,8 +92,9 @@ func (c *eveningDetectiveClient) GetTeams(ctx context.Context, in *GetTeamsReq,
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveClient) GetTeamsCSV(ctx context.Context, in *GetTeamsCSVReq, opts ...grpc.CallOption) (*GetTeamsCSVRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetTeamsCSVRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_GetTeamsCSV_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_GetTeamsCSV_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -92,17 +102,9 @@ func (c *eveningDetectiveClient) GetTeamsCSV(ctx context.Context, in *GetTeamsCS
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveClient) GetTeam(ctx context.Context, in *GetTeamReq, opts ...grpc.CallOption) (*GetTeamRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetTeamRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_GetTeam_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveClient) DeleteTeams(ctx context.Context, in *DeleteTeamsReq, opts ...grpc.CallOption) (*DeleteTeamsRsp, error) {
|
||||
out := new(DeleteTeamsRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_DeleteTeams_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_GetTeam_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -110,8 +112,19 @@ func (c *eveningDetectiveClient) DeleteTeams(ctx context.Context, in *DeleteTeam
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveClient) AddAction(ctx context.Context, in *AddActionReq, opts ...grpc.CallOption) (*AddActionRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(AddActionRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_AddAction_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_AddAction_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveClient) GetGame(ctx context.Context, in *GetGameReq, opts ...grpc.CallOption) (*GetGameRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetGameRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_GetGame_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -119,8 +132,9 @@ func (c *eveningDetectiveClient) AddAction(ctx context.Context, in *AddActionReq
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveClient) GameStart(ctx context.Context, in *GameStartReq, opts ...grpc.CallOption) (*GameStartRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GameStartRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_GameStart_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_GameStart_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -128,8 +142,9 @@ func (c *eveningDetectiveClient) GameStart(ctx context.Context, in *GameStartReq
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveClient) GameStop(ctx context.Context, in *GameStopReq, opts ...grpc.CallOption) (*GameStopRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GameStopRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_GameStop_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_GameStop_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -137,8 +152,39 @@ func (c *eveningDetectiveClient) GameStop(ctx context.Context, in *GameStopReq,
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveClient) GiveApplications(ctx context.Context, in *GiveApplicationsReq, opts ...grpc.CallOption) (*GiveApplicationsRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GiveApplicationsRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_GiveApplications_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_GiveApplications_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveClient) DownloadTeamsQrCodesFile(ctx context.Context, in *DownloadTeamsQrCodesFileReq, opts ...grpc.CallOption) (*DownloadTeamsQrCodesFileRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(DownloadTeamsQrCodesFileRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_DownloadTeamsQrCodesFile_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveClient) GetGraph(ctx context.Context, in *GetGraphReq, opts ...grpc.CallOption) (*GetGraphRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetGraphRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_GetGraph_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *eveningDetectiveClient) UpdateNode(ctx context.Context, in *UpdateNodeReq, opts ...grpc.CallOption) (*UpdateNodeRsp, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(UpdateNodeRsp)
|
||||
err := c.cc.Invoke(ctx, EveningDetective_UpdateNode_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -147,24 +193,30 @@ func (c *eveningDetectiveClient) GiveApplications(ctx context.Context, in *GiveA
|
||||
|
||||
// EveningDetectiveServer is the server API for EveningDetective service.
|
||||
// All implementations must embed UnimplementedEveningDetectiveServer
|
||||
// for forward compatibility
|
||||
// for forward compatibility.
|
||||
type EveningDetectiveServer interface {
|
||||
Ping(context.Context, *PingReq) (*PingRsp, error)
|
||||
AddTeams(context.Context, *AddTeamsReq) (*AddTeamsRsp, error)
|
||||
GetTeams(context.Context, *GetTeamsReq) (*GetTeamsRsp, error)
|
||||
GetTeamsCSV(context.Context, *GetTeamsCSVReq) (*GetTeamsCSVRsp, error)
|
||||
GetTeam(context.Context, *GetTeamReq) (*GetTeamRsp, error)
|
||||
DeleteTeams(context.Context, *DeleteTeamsReq) (*DeleteTeamsRsp, error)
|
||||
AddAction(context.Context, *AddActionReq) (*AddActionRsp, error)
|
||||
GetGame(context.Context, *GetGameReq) (*GetGameRsp, error)
|
||||
GameStart(context.Context, *GameStartReq) (*GameStartRsp, error)
|
||||
GameStop(context.Context, *GameStopReq) (*GameStopRsp, error)
|
||||
GiveApplications(context.Context, *GiveApplicationsReq) (*GiveApplicationsRsp, error)
|
||||
DownloadTeamsQrCodesFile(context.Context, *DownloadTeamsQrCodesFileReq) (*DownloadTeamsQrCodesFileRsp, error)
|
||||
GetGraph(context.Context, *GetGraphReq) (*GetGraphRsp, error)
|
||||
UpdateNode(context.Context, *UpdateNodeReq) (*UpdateNodeRsp, error)
|
||||
mustEmbedUnimplementedEveningDetectiveServer()
|
||||
}
|
||||
|
||||
// UnimplementedEveningDetectiveServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedEveningDetectiveServer struct {
|
||||
}
|
||||
// UnimplementedEveningDetectiveServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedEveningDetectiveServer struct{}
|
||||
|
||||
func (UnimplementedEveningDetectiveServer) Ping(context.Context, *PingReq) (*PingRsp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented")
|
||||
@@ -181,12 +233,12 @@ func (UnimplementedEveningDetectiveServer) GetTeamsCSV(context.Context, *GetTeam
|
||||
func (UnimplementedEveningDetectiveServer) GetTeam(context.Context, *GetTeamReq) (*GetTeamRsp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetTeam not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServer) DeleteTeams(context.Context, *DeleteTeamsReq) (*DeleteTeamsRsp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteTeams not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServer) AddAction(context.Context, *AddActionReq) (*AddActionRsp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AddAction not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServer) GetGame(context.Context, *GetGameReq) (*GetGameRsp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetGame not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServer) GameStart(context.Context, *GameStartReq) (*GameStartRsp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GameStart not implemented")
|
||||
}
|
||||
@@ -196,7 +248,17 @@ func (UnimplementedEveningDetectiveServer) GameStop(context.Context, *GameStopRe
|
||||
func (UnimplementedEveningDetectiveServer) GiveApplications(context.Context, *GiveApplicationsReq) (*GiveApplicationsRsp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GiveApplications not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServer) DownloadTeamsQrCodesFile(context.Context, *DownloadTeamsQrCodesFileReq) (*DownloadTeamsQrCodesFileRsp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DownloadTeamsQrCodesFile not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServer) GetGraph(context.Context, *GetGraphReq) (*GetGraphRsp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetGraph not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServer) UpdateNode(context.Context, *UpdateNodeReq) (*UpdateNodeRsp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateNode not implemented")
|
||||
}
|
||||
func (UnimplementedEveningDetectiveServer) mustEmbedUnimplementedEveningDetectiveServer() {}
|
||||
func (UnimplementedEveningDetectiveServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeEveningDetectiveServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to EveningDetectiveServer will
|
||||
@@ -206,6 +268,13 @@ type UnsafeEveningDetectiveServer interface {
|
||||
}
|
||||
|
||||
func RegisterEveningDetectiveServer(s grpc.ServiceRegistrar, srv EveningDetectiveServer) {
|
||||
// If the following call pancis, it indicates UnimplementedEveningDetectiveServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&EveningDetective_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
@@ -299,24 +368,6 @@ func _EveningDetective_GetTeam_Handler(srv interface{}, ctx context.Context, dec
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetective_DeleteTeams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DeleteTeamsReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EveningDetectiveServer).DeleteTeams(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: EveningDetective_DeleteTeams_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EveningDetectiveServer).DeleteTeams(ctx, req.(*DeleteTeamsReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetective_AddAction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(AddActionReq)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -335,6 +386,24 @@ func _EveningDetective_AddAction_Handler(srv interface{}, ctx context.Context, d
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetective_GetGame_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetGameReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EveningDetectiveServer).GetGame(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: EveningDetective_GetGame_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EveningDetectiveServer).GetGame(ctx, req.(*GetGameReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetective_GameStart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GameStartReq)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -389,6 +458,60 @@ func _EveningDetective_GiveApplications_Handler(srv interface{}, ctx context.Con
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetective_DownloadTeamsQrCodesFile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DownloadTeamsQrCodesFileReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EveningDetectiveServer).DownloadTeamsQrCodesFile(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: EveningDetective_DownloadTeamsQrCodesFile_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EveningDetectiveServer).DownloadTeamsQrCodesFile(ctx, req.(*DownloadTeamsQrCodesFileReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetective_GetGraph_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetGraphReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EveningDetectiveServer).GetGraph(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: EveningDetective_GetGraph_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EveningDetectiveServer).GetGraph(ctx, req.(*GetGraphReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _EveningDetective_UpdateNode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UpdateNodeReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(EveningDetectiveServer).UpdateNode(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: EveningDetective_UpdateNode_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(EveningDetectiveServer).UpdateNode(ctx, req.(*UpdateNodeReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// EveningDetective_ServiceDesc is the grpc.ServiceDesc for EveningDetective service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
@@ -416,14 +539,14 @@ var EveningDetective_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "GetTeam",
|
||||
Handler: _EveningDetective_GetTeam_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteTeams",
|
||||
Handler: _EveningDetective_DeleteTeams_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "AddAction",
|
||||
Handler: _EveningDetective_AddAction_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetGame",
|
||||
Handler: _EveningDetective_GetGame_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GameStart",
|
||||
Handler: _EveningDetective_GameStart_Handler,
|
||||
@@ -436,6 +559,18 @@ var EveningDetective_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "GiveApplications",
|
||||
Handler: _EveningDetective_GiveApplications_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DownloadTeamsQrCodesFile",
|
||||
Handler: _EveningDetective_DownloadTeamsQrCodesFile_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetGraph",
|
||||
Handler: _EveningDetective_GetGraph_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UpdateNode",
|
||||
Handler: _EveningDetective_UpdateNode_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "main.proto",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
import{_ as o,c as s,a as t,o as a}from"./index-waNsD4Su.js";const n={},c={class:"about"};function r(_,e){return a(),s("div",c,e[0]||(e[0]=[t("h1",null,"This is an about page",-1)]))}const l=o(n,[["render",r]]);export{l as default};
|
||||
@@ -1 +0,0 @@
|
||||
@media (min-width: 1024px){.about{min-height:100vh;display:flex;align-items:center}}
|
||||
@@ -1 +0,0 @@
|
||||
:root{--vt-c-white: #ffffff;--vt-c-white-soft: #f8f8f8;--vt-c-white-mute: #f2f2f2;--vt-c-black: #181818;--vt-c-black-soft: #222222;--vt-c-black-mute: #282828;--vt-c-indigo: #2c3e50;--vt-c-divider-light-1: rgba(60, 60, 60, .29);--vt-c-divider-light-2: rgba(60, 60, 60, .12);--vt-c-divider-dark-1: rgba(84, 84, 84, .65);--vt-c-divider-dark-2: rgba(84, 84, 84, .48);--vt-c-text-light-1: var(--vt-c-indigo);--vt-c-text-light-2: rgba(60, 60, 60, .66);--vt-c-text-dark-1: var(--vt-c-white);--vt-c-text-dark-2: rgba(235, 235, 235, .64)}:root{--color-background: var(--vt-c-white);--color-background-soft: var(--vt-c-white-soft);--color-background-mute: var(--vt-c-white-mute);--color-border: var(--vt-c-divider-light-2);--color-border-hover: var(--vt-c-divider-light-1);--color-heading: var(--vt-c-text-light-1);--color-text: var(--vt-c-text-light-1);--section-gap: 160px}@media (prefers-color-scheme: dark){:root{--color-background: var(--vt-c-black);--color-background-soft: var(--vt-c-black-soft);--color-background-mute: var(--vt-c-black-mute);--color-border: var(--vt-c-divider-dark-2);--color-border-hover: var(--vt-c-divider-dark-1);--color-heading: var(--vt-c-text-dark-1);--color-text: var(--vt-c-text-dark-2)}}*,*:before,*:after{box-sizing:border-box;margin:0;font-weight:400}body{min-height:100vh;color:var(--color-text);background:var(--color-background);transition:color .5s,background-color .5s;line-height:1.6;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:15px;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#app{max-width:1280px;margin:0 auto;padding:2rem;font-weight:400}a,.green{text-decoration:none;color:#00bd7e;transition:.4s;padding:3px}@media (hover: hover){a:hover{background-color:#00bd7e33}}@media (min-width: 1024px){body{display:flex;place-items:center}#app{display:grid;grid-template-columns:1fr 1fr;padding:0 2rem}}h1[data-v-d1bb330e]{font-weight:500;font-size:2.6rem;position:relative;top:-10px}h3[data-v-d1bb330e]{font-size:1.2rem}.greetings h1[data-v-d1bb330e],.greetings h3[data-v-d1bb330e]{text-align:center}@media (min-width: 1024px){.greetings h1[data-v-d1bb330e],.greetings h3[data-v-d1bb330e]{text-align:left}}header[data-v-4bc5e6bf]{line-height:1.5;max-height:100vh}.logo[data-v-4bc5e6bf]{display:block;margin:0 auto 2rem}nav[data-v-4bc5e6bf]{width:100%;font-size:12px;text-align:center;margin-top:2rem}nav a.router-link-exact-active[data-v-4bc5e6bf]{color:var(--color-text)}nav a.router-link-exact-active[data-v-4bc5e6bf]:hover{background-color:transparent}nav a[data-v-4bc5e6bf]{display:inline-block;padding:0 1rem;border-left:1px solid var(--color-border)}nav a[data-v-4bc5e6bf]:first-of-type{border:0}@media (min-width: 1024px){header[data-v-4bc5e6bf]{display:flex;place-items:center;padding-right:calc(var(--section-gap) / 2)}.logo[data-v-4bc5e6bf]{margin:0 2rem 0 0}header .wrapper[data-v-4bc5e6bf]{display:flex;place-items:flex-start;flex-wrap:wrap}nav[data-v-4bc5e6bf]{text-align:left;margin-left:-1rem;font-size:1rem;padding:1rem 0;margin-top:1rem}}.item[data-v-fd0742eb]{margin-top:2rem;display:flex;position:relative}.details[data-v-fd0742eb]{flex:1;margin-left:1rem}i[data-v-fd0742eb]{display:flex;place-items:center;place-content:center;width:32px;height:32px;color:var(--color-text)}h3[data-v-fd0742eb]{font-size:1.2rem;font-weight:500;margin-bottom:.4rem;color:var(--color-heading)}@media (min-width: 1024px){.item[data-v-fd0742eb]{margin-top:0;padding:.4rem 0 1rem calc(var(--section-gap) / 2)}i[data-v-fd0742eb]{top:calc(50% - 25px);left:-26px;position:absolute;border:1px solid var(--color-border);background:var(--color-background);border-radius:8px;width:50px;height:50px}.item[data-v-fd0742eb]:before{content:" ";border-left:1px solid var(--color-border);position:absolute;left:0;bottom:calc(50% + 25px);height:calc(50% - 25px)}.item[data-v-fd0742eb]:after{content:" ";border-left:1px solid var(--color-border);position:absolute;left:0;top:calc(50% + 25px);height:calc(50% - 25px)}.item[data-v-fd0742eb]:first-of-type:before{display:none}.item[data-v-fd0742eb]:last-of-type:after{display:none}}
|
||||
@@ -1 +0,0 @@
|
||||
@media (min-width: 1024px){.about{min-height:100vh;display:flex;align-items:center}}
|
||||
@@ -1 +0,0 @@
|
||||
import{_ as o,c as s,a as t,o as a}from"./index-BKrme-n6.js";const n={},c={class:"about"};function r(_,e){return a(),s("div",c,e[0]||(e[0]=[t("h1",null,"This is an about page",-1)]))}const l=o(n,[["render",r]]);export{l as default};
|
||||
@@ -1 +0,0 @@
|
||||
:root{--vt-c-white: #ffffff;--vt-c-white-soft: #f8f8f8;--vt-c-white-mute: #f2f2f2;--vt-c-black: #181818;--vt-c-black-soft: #222222;--vt-c-black-mute: #282828;--vt-c-indigo: #2c3e50;--vt-c-divider-light-1: rgba(60, 60, 60, .29);--vt-c-divider-light-2: rgba(60, 60, 60, .12);--vt-c-divider-dark-1: rgba(84, 84, 84, .65);--vt-c-divider-dark-2: rgba(84, 84, 84, .48);--vt-c-text-light-1: var(--vt-c-indigo);--vt-c-text-light-2: rgba(60, 60, 60, .66);--vt-c-text-dark-1: var(--vt-c-white);--vt-c-text-dark-2: rgba(235, 235, 235, .64)}:root{--color-background: var(--vt-c-white);--color-background-soft: var(--vt-c-white-soft);--color-background-mute: var(--vt-c-white-mute);--color-border: var(--vt-c-divider-light-2);--color-border-hover: var(--vt-c-divider-light-1);--color-heading: var(--vt-c-text-light-1);--color-text: var(--vt-c-text-light-1);--section-gap: 160px}@media (prefers-color-scheme: dark){:root{--color-background: var(--vt-c-black);--color-background-soft: var(--vt-c-black-soft);--color-background-mute: var(--vt-c-black-mute);--color-border: var(--vt-c-divider-dark-2);--color-border-hover: var(--vt-c-divider-dark-1);--color-heading: var(--vt-c-text-dark-1);--color-text: var(--vt-c-text-dark-2)}}*,*:before,*:after{box-sizing:border-box;margin:0;font-weight:400}body{min-height:100vh;color:var(--color-text);background:var(--color-background);transition:color .5s,background-color .5s;line-height:1.6;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:15px;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#app{max-width:1280px;margin:0 auto;padding:2rem;font-weight:400}a,.green{text-decoration:none;color:#00bd7e;transition:.4s;padding:3px}@media (hover: hover){a:hover{background-color:#00bd7e33}}@media (min-width: 1024px){body{display:flex;place-items:center}#app{display:grid;grid-template-columns:1fr 1fr;padding:0 2rem}}h1[data-v-d1bb330e]{font-weight:500;font-size:2.6rem;position:relative;top:-10px}h3[data-v-d1bb330e]{font-size:1.2rem}.greetings h1[data-v-d1bb330e],.greetings h3[data-v-d1bb330e]{text-align:center}@media (min-width: 1024px){.greetings h1[data-v-d1bb330e],.greetings h3[data-v-d1bb330e]{text-align:left}}header[data-v-cf9c2f5c]{line-height:1.5;max-height:100vh}.logo[data-v-cf9c2f5c]{display:block;margin:0 auto 2rem}nav[data-v-cf9c2f5c]{width:100%;font-size:12px;text-align:center;margin-top:2rem}nav a.router-link-exact-active[data-v-cf9c2f5c]{color:var(--color-text)}nav a.router-link-exact-active[data-v-cf9c2f5c]:hover{background-color:transparent}nav a[data-v-cf9c2f5c]{display:inline-block;padding:0 1rem;border-left:1px solid var(--color-border)}nav a[data-v-cf9c2f5c]:first-of-type{border:0}@media (min-width: 1024px){header[data-v-cf9c2f5c]{display:flex;place-items:center;padding-right:calc(var(--section-gap) / 2)}.logo[data-v-cf9c2f5c]{margin:0 2rem 0 0}header .wrapper[data-v-cf9c2f5c]{display:flex;place-items:flex-start;flex-wrap:wrap}nav[data-v-cf9c2f5c]{text-align:left;margin-left:-1rem;font-size:1rem;padding:1rem 0;margin-top:1rem}}.item[data-v-fd0742eb]{margin-top:2rem;display:flex;position:relative}.details[data-v-fd0742eb]{flex:1;margin-left:1rem}i[data-v-fd0742eb]{display:flex;place-items:center;place-content:center;width:32px;height:32px;color:var(--color-text)}h3[data-v-fd0742eb]{font-size:1.2rem;font-weight:500;margin-bottom:.4rem;color:var(--color-heading)}@media (min-width: 1024px){.item[data-v-fd0742eb]{margin-top:0;padding:.4rem 0 1rem calc(var(--section-gap) / 2)}i[data-v-fd0742eb]{top:calc(50% - 25px);left:-26px;position:absolute;border:1px solid var(--color-border);background:var(--color-background);border-radius:8px;width:50px;height:50px}.item[data-v-fd0742eb]:before{content:" ";border-left:1px solid var(--color-border);position:absolute;left:0;bottom:calc(50% + 25px);height:calc(50% - 25px)}.item[data-v-fd0742eb]:after{content:" ";border-left:1px solid var(--color-border);position:absolute;left:0;top:calc(50% + 25px);height:calc(50% - 25px)}.item[data-v-fd0742eb]:first-of-type:before{display:none}.item[data-v-fd0742eb]:last-of-type:after{display:none}}
|
||||
|
Before Width: | Height: | Size: 4.2 KiB |