Compare commits
112 Commits
49999e9e70
..
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 |
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
# Dependency directories (remove the comment below to include it)
|
||||||
# vendor/
|
# vendor/
|
||||||
|
bin/
|
||||||
|
|
||||||
# Go workspace file
|
# Go workspace file
|
||||||
go.work
|
go.work
|
||||||
@@ -25,3 +26,7 @@ go.work
|
|||||||
go.sum
|
go.sum
|
||||||
|
|
||||||
store.db
|
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,7 +1,58 @@
|
|||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
|
"ввода",
|
||||||
|
"внимательно",
|
||||||
|
"вопрос",
|
||||||
|
"второй",
|
||||||
|
"дает",
|
||||||
|
"Дело",
|
||||||
|
"Детективы",
|
||||||
|
"диалога",
|
||||||
|
"диалогом",
|
||||||
|
"другой",
|
||||||
|
"задать",
|
||||||
|
"запущен",
|
||||||
|
"корректный",
|
||||||
|
"Можно",
|
||||||
|
"Название",
|
||||||
|
"найдена",
|
||||||
|
"найдено",
|
||||||
|
"Нельзя",
|
||||||
|
"Открываем",
|
||||||
|
"Открытие",
|
||||||
|
"открытую",
|
||||||
|
"открыть",
|
||||||
|
"получение",
|
||||||
|
"после",
|
||||||
|
"правила",
|
||||||
|
"Приложение",
|
||||||
|
"приложением",
|
||||||
|
"проходами",
|
||||||
|
"проходом",
|
||||||
|
"сервер",
|
||||||
|
"скрытой",
|
||||||
|
"скрытую",
|
||||||
|
"существует",
|
||||||
|
"сходить",
|
||||||
|
"Такой",
|
||||||
|
"Текст",
|
||||||
|
"Точка",
|
||||||
|
"точки",
|
||||||
|
"точку",
|
||||||
|
"читайте",
|
||||||
"AUTOINCREMENT",
|
"AUTOINCREMENT",
|
||||||
|
"GOARCH",
|
||||||
|
"gopdf",
|
||||||
"gwmux",
|
"gwmux",
|
||||||
"palces"
|
"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
|
FROM alpine:latest
|
||||||
RUN apk add --no-cache ca-certificates
|
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
|
RUN chmod +x /usr/local/bin/evening_detective
|
||||||
CMD ["/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:
|
run:
|
||||||
go run ./cmd/evening_detective/main.go
|
go run ./cmd/evening_detective/main.go
|
||||||
|
|
||||||
build:
|
build-builder:
|
||||||
go build -o bin/evening_detective cmd/evening_detective/main.go
|
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
|
Init
|
||||||
|
|
||||||
@@ -14,3 +14,9 @@ go mod tidy
|
|||||||
```shell
|
```shell
|
||||||
make run
|
make run
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Сборка
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make build
|
||||||
|
```
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ service EveningDetective {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpc GetGame(GetGameReq) returns (GetGameRsp) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/game"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
rpc GameStart(GameStartReq) returns (GameStartRsp) {
|
rpc GameStart(GameStartReq) returns (GameStartRsp) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/game/start",
|
post: "/game/start",
|
||||||
@@ -65,6 +71,25 @@ service EveningDetective {
|
|||||||
body: "*"
|
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 {}
|
message PingReq {}
|
||||||
@@ -114,6 +139,13 @@ message Application {
|
|||||||
int64 id = 1;
|
int64 id = 1;
|
||||||
string name = 2;
|
string name = 2;
|
||||||
string state = 3;
|
string state = 3;
|
||||||
|
string number = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Door {
|
||||||
|
string code = 1;
|
||||||
|
string name = 2;
|
||||||
|
bool show = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetTeamReq {}
|
message GetTeamReq {}
|
||||||
@@ -134,7 +166,18 @@ message Action {
|
|||||||
string place = 2;
|
string place = 2;
|
||||||
string name = 3;
|
string name = 3;
|
||||||
string text = 4;
|
string text = 4;
|
||||||
repeated Application applications = 5;
|
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 {}
|
message GameStartReq {}
|
||||||
@@ -153,3 +196,52 @@ message GiveApplicationsReq {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message GiveApplicationsRsp {}
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
### Получение игры
|
||||||
|
|
||||||
|
GET http://localhost:8090/game
|
||||||
|
|
||||||
|
### Старт игры
|
||||||
|
|
||||||
|
POST http://localhost:8090/game/start
|
||||||
|
|
||||||
|
### Стоп игры
|
||||||
|
|
||||||
|
POST http://localhost:8090/game/stop
|
||||||
|
|
||||||
### Получение списка комад
|
### Получение списка комад
|
||||||
|
|
||||||
GET http://localhost:8090/teams
|
GET http://localhost:8090/teams
|
||||||
@@ -45,3 +57,7 @@ POST http://localhost:8090/teams/1/applications
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
### Qr коды команд в pdf
|
||||||
|
|
||||||
|
GET http://localhost:8090/teams/pdf
|
||||||
|
|||||||
@@ -2,13 +2,24 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"embed"
|
||||||
"evening_detective/internal/app"
|
"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"
|
||||||
|
"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_service"
|
||||||
|
"evening_detective/internal/services/story_storage"
|
||||||
proto "evening_detective/proto"
|
proto "evening_detective/proto"
|
||||||
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
@@ -16,39 +27,75 @@ import (
|
|||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed static/user/*
|
||||||
|
var userFS embed.FS
|
||||||
|
|
||||||
|
//go:embed static/admin/*
|
||||||
|
var adminFS embed.FS
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Create a listener on TCP port
|
grpcGatewayHost := config.GetGrpcGatewayHost()
|
||||||
lis, err := net.Listen("tcp", ":8080")
|
userClientHost := config.GetUserClientHost()
|
||||||
if err != nil {
|
adminClientHost := config.GetAdminClientHost()
|
||||||
log.Fatalln("Failed to listen:", err)
|
fileHost := config.GetFileHost()
|
||||||
}
|
|
||||||
|
|
||||||
// Create a gRPC server object
|
// Create a gRPC server object
|
||||||
s := grpc.NewServer()
|
s := grpc.NewServer()
|
||||||
// Attach the Greeter service to the server
|
// Attach the Greeter service to the server
|
||||||
repository, err := services.NewRepository()
|
dbFilepath := config.GetDBFilepath()
|
||||||
|
|
||||||
|
dbService, err := db.NewDBService(dbFilepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
storyService, err := story_service.NewStoryService()
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
linkService := link.NewLinkService(userClientHost)
|
||||||
|
|
||||||
|
passwordGenerator := password.NewPasswordGenerator()
|
||||||
|
|
||||||
|
pdfGenerator := pdf.NewPDFGenerator()
|
||||||
|
|
||||||
proto.RegisterEveningDetectiveServer(
|
proto.RegisterEveningDetectiveServer(
|
||||||
s,
|
s,
|
||||||
app.NewServer(
|
app.NewServer(
|
||||||
services.NewServices(
|
services.NewServices(
|
||||||
repository,
|
dbService,
|
||||||
storyService,
|
storyService,
|
||||||
|
linkService,
|
||||||
|
passwordGenerator,
|
||||||
|
pdfGenerator,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
// Serve gRPC server
|
|
||||||
log.Println("Serving gRPC on 0.0.0.0:8080")
|
// Server gRPC
|
||||||
|
lis, err := net.Listen("tcp", ":8080")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Failed to listen:", err)
|
||||||
|
}
|
||||||
go func() {
|
go func() {
|
||||||
log.Fatalln(s.Serve(lis))
|
log.Fatalln(s.Serve(lis))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Client gRPC
|
||||||
// Create a client connection to the gRPC server we just started
|
// Create a client connection to the gRPC server we just started
|
||||||
// This is where the gRPC-Gateway proxies the requests
|
// This is where the gRPC-Gateway proxies the requests
|
||||||
conn, err := grpc.NewClient(
|
conn, err := grpc.NewClient(
|
||||||
@@ -75,40 +122,59 @@ func main() {
|
|||||||
log.Fatalln("Failed to register gateway:", err)
|
log.Fatalln("Failed to register gateway:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Server gRPC-Gateway
|
||||||
gwServer := &http.Server{
|
gwServer := &http.Server{
|
||||||
Addr: ":8090",
|
Addr: config.GrpcGatewayPort,
|
||||||
Handler: cors(gwmux),
|
Handler: cors(gwmux),
|
||||||
}
|
}
|
||||||
|
log.Printf("Serving %s for gRPC-Gateway\n", grpcGatewayHost)
|
||||||
// Serve gRPC-Gateway server
|
|
||||||
log.Println("Serving gRPC-Gateway on http://0.0.0.0:8090")
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Fatalln(gwServer.ListenAndServe())
|
log.Fatalln(gwServer.ListenAndServe())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
muxUser := http.NewServeMux()
|
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)
|
muxUser.Handle("/", fileServerUser)
|
||||||
|
|
||||||
// Serve user web server
|
// Server user web
|
||||||
log.Println("Serving user web on http://0.0.0.0:8100")
|
log.Printf("Serving %s for user web\n", userClientHost)
|
||||||
go func() {
|
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()
|
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)
|
muxAdmin.Handle("/", fileServerAdmin)
|
||||||
|
|
||||||
// Serve admin web server
|
// Server admin web
|
||||||
log.Println("Serving admin web on http://0.0.0.0:8110")
|
adminWebServer := &http.Server{
|
||||||
log.Fatalln(http.ListenAndServe(":8110", muxAdmin))
|
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 {
|
func cors(h http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE")
|
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")
|
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, Authorization, ResponseType, X-Id, X-Password")
|
||||||
if r.Method == "OPTIONS" {
|
if r.Method == "OPTIONS" {
|
||||||
return
|
return
|
||||||
@@ -116,3 +182,14 @@ func cors(h http.Handler) http.Handler {
|
|||||||
h.ServeHTTP(w, r)
|
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 |
@@ -5,8 +5,8 @@
|
|||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>ВД Админка</title>
|
<title>ВД Админка</title>
|
||||||
<script type="module" crossorigin src="/assets/index-DNz9l3r8.js"></script>
|
<script type="module" crossorigin src="/assets/index-c8po_p3Q.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-BJ3dZ1mc.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-CgpxTv-m.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<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 |
@@ -5,8 +5,8 @@
|
|||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Вечерний детектив</title>
|
<title>Вечерний детектив</title>
|
||||||
<script type="module" crossorigin src="/assets/index-DuNBa2Ey.js"></script>
|
<script type="module" crossorigin src="/assets/index-CQcj9qbi.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-BdIvr4cN.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-C2dfznw-.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<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: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8090:8090" # api
|
||||||
# environment:
|
- "8100:8100" # user
|
||||||
# - ENV_VAR_NAME=env_var_value
|
- "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,18 +1,35 @@
|
|||||||
module evening_detective
|
module evening_detective
|
||||||
|
|
||||||
go 1.22
|
go 1.26
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0
|
github.com/golang/mock v1.6.0
|
||||||
google.golang.org/grpc v1.64.0
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2
|
||||||
|
google.golang.org/grpc v1.75.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
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 (
|
require (
|
||||||
github.com/mattn/go-sqlite3 v1.14.28
|
github.com/mattn/go-sqlite3 v1.14.28
|
||||||
golang.org/x/net v0.23.0 // indirect
|
github.com/signintech/gopdf v0.32.0
|
||||||
golang.org/x/sys v0.18.0 // indirect
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
golang.org/x/text v0.15.0 // indirect
|
golang.org/x/net v0.41.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
|
golang.org/x/text v0.28.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // 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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ func (s *Server) AddAction(ctx context.Context, req *proto.AddActionReq) (*proto
|
|||||||
return s.services.AddAction(ctx, req)
|
return s.services.AddAction(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) GetGame(ctx context.Context, req *proto.GetGameReq) (*proto.GetGameRsp, error) {
|
||||||
|
return s.services.GetGame(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) GameStart(ctx context.Context, req *proto.GameStartReq) (*proto.GameStartRsp, error) {
|
func (s *Server) GameStart(ctx context.Context, req *proto.GameStartReq) (*proto.GameStartRsp, error) {
|
||||||
return s.services.GameStart(ctx, req)
|
return s.services.GameStart(ctx, req)
|
||||||
}
|
}
|
||||||
@@ -55,3 +59,15 @@ func (s *Server) GameStop(ctx context.Context, req *proto.GameStopReq) (*proto.G
|
|||||||
func (s *Server) GiveApplications(ctx context.Context, req *proto.GiveApplicationsReq) (*proto.GiveApplicationsRsp, error) {
|
func (s *Server) GiveApplications(ctx context.Context, req *proto.GiveApplicationsReq) (*proto.GiveApplicationsRsp, error) {
|
||||||
return s.services.GiveApplications(ctx, req)
|
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
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
type Game struct {
|
type Game struct {
|
||||||
StartTime int64
|
|
||||||
EndTime int64
|
|
||||||
State string
|
State string
|
||||||
|
StartTime string
|
||||||
|
EndTime string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ type Team struct {
|
|||||||
ID int64
|
ID int64
|
||||||
Name string
|
Name string
|
||||||
Password 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
|
||||||
|
}
|
||||||
@@ -6,8 +6,14 @@ var (
|
|||||||
letters = []rune("abcdefghijklmnopqrstuvwxyz123456789")
|
letters = []rune("abcdefghijklmnopqrstuvwxyz123456789")
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenPass(n int) string {
|
type service struct{}
|
||||||
b := make([]rune, n)
|
|
||||||
|
func NewPasswordGenerator() IPasswordGenerator {
|
||||||
|
return &service{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) GeneratePassword(length int) string {
|
||||||
|
b := make([]rune, length)
|
||||||
for i := range b {
|
for i := range b {
|
||||||
b[i] = letters[rand.Intn(len(letters))]
|
b[i] = letters[rand.Intn(len(letters))]
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -2,8 +2,9 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"evening_detective/internal/models"
|
"evening_detective/internal/models"
|
||||||
"evening_detective/internal/services/story_service"
|
story_service_models "evening_detective/internal/services/story_service/models"
|
||||||
"evening_detective/proto"
|
"evening_detective/proto"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mapTeamsToTeamAdvanced(team *models.Team) *proto.TeamAdvanced {
|
func mapTeamsToTeamAdvanced(team *models.Team) *proto.TeamAdvanced {
|
||||||
@@ -24,24 +25,32 @@ func mapTeamsToTeamFull(team *models.Team) *proto.TeamFull {
|
|||||||
|
|
||||||
func mapProtoTeamsToTeam(team *proto.Team) *models.Team {
|
func mapProtoTeamsToTeam(team *proto.Team) *models.Team {
|
||||||
return &models.Team{
|
return &models.Team{
|
||||||
Name: team.Name,
|
Name: clearTeamName(team.Name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapActionToProtoAction(action *models.Action) *proto.Action {
|
func mapPlaceToProtoAction(place *story_service_models.Place) *proto.Action {
|
||||||
return &proto.Action{
|
return &proto.Action{
|
||||||
Id: action.ID,
|
Place: place.Code,
|
||||||
Place: action.Place,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapStoryApplicationToProtoApplication(application *story_service.Application) *proto.Application {
|
func mapStoryApplicationToProtoApplication(application *story_service_models.Application) *proto.Application {
|
||||||
return &proto.Application{
|
return &proto.Application{
|
||||||
Name: application.Name,
|
Name: application.Name,
|
||||||
|
Number: application.Number,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapStoryApplicationsToApplications(applications []*story_service.Application) []*models.Application {
|
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))
|
res := make([]*models.Application, 0, len(applications))
|
||||||
for _, application := range applications {
|
for _, application := range applications {
|
||||||
res = append(res, mapStoryApplicationToApplication(application))
|
res = append(res, mapStoryApplicationToApplication(application))
|
||||||
@@ -49,7 +58,7 @@ func mapStoryApplicationsToApplications(applications []*story_service.Applicatio
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapStoryApplicationToApplication(application *story_service.Application) *models.Application {
|
func mapStoryApplicationToApplication(application *story_service_models.Application) *models.Application {
|
||||||
return &models.Application{
|
return &models.Application{
|
||||||
Name: application.Name,
|
Name: application.Name,
|
||||||
State: "NEW",
|
State: "NEW",
|
||||||
@@ -85,3 +94,7 @@ func mapProtoApplicationToApplication(items *proto.Application) *models.Applicat
|
|||||||
ID: items.Id,
|
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
|
||||||
|
}
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"evening_detective/internal/models"
|
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Repository struct {
|
|
||||||
db *sql.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRepository() (*Repository, error) {
|
|
||||||
db, err := sql.Open("sqlite3", "store.db")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, err = db.Exec("CREATE TABLE IF NOT EXISTS teams (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, 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);")
|
|
||||||
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);")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Repository{db: db}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repository) GetTeams(ctx context.Context) ([]*models.Team, error) {
|
|
||||||
rows, err := r.db.Query("select id, name, password from teams")
|
|
||||||
if err != nil {
|
|
||||||
panic(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 (r *Repository) AddTeams(ctx context.Context, teams []*models.Team) ([]*models.Team, error) {
|
|
||||||
for _, team := range teams {
|
|
||||||
result, err := r.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 (r *Repository) GetActions(ctx context.Context, teamId int64) ([]*models.Action, error) {
|
|
||||||
rows, err := r.db.Query("select id, place from actions where teamId = $1", teamId)
|
|
||||||
if err != nil {
|
|
||||||
panic(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 (r *Repository) AddActions(ctx context.Context, teamId int64, actions []*models.Action) error {
|
|
||||||
for _, action := range actions {
|
|
||||||
_, err := r.db.Exec("insert into actions (place, teamId) values ($1, $2)", action.Place, teamId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repository) GetTeam(ctx context.Context, teamId any, password any) (*models.Team, error) {
|
|
||||||
rows, err := r.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 (r *Repository) AddApplications(ctx context.Context, actions []*models.Action) error {
|
|
||||||
for _, action := range actions {
|
|
||||||
for _, application := range action.Applications {
|
|
||||||
_, err := r.db.Exec("insert into applications (name, teamId, state) values ($1, $2, $3)", application.Name, action.TeamID, application.State)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repository) GetApplications(ctx context.Context, teamId int64, state string) ([]*models.Application, error) {
|
|
||||||
rows, err := r.db.Query("select id, name from applications where teamId = $1 and state = $2", teamId, state)
|
|
||||||
if err != nil {
|
|
||||||
panic(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 (r *Repository) GiveApplications(ctx context.Context, teamId int64, applications []*models.Application) error {
|
|
||||||
for _, application := range applications {
|
|
||||||
_, err := r.db.Exec("update applications set state = \"gave\" where teamId = $1 and id = $2", teamId, application.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -6,11 +6,13 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"evening_detective/internal/models"
|
"evening_detective/internal/models"
|
||||||
"evening_detective/internal/modules/password"
|
"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/internal/services/story_service"
|
||||||
"evening_detective/proto"
|
"evening_detective/proto"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"time"
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
@@ -18,34 +20,61 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Services struct {
|
type Services struct {
|
||||||
repository *Repository
|
dbService db.IDBService
|
||||||
storyService *story_service.StoryService
|
storyService *story_service.StoryService
|
||||||
|
linkService link.ILinkService
|
||||||
|
passwordGenerator password.IPasswordGenerator
|
||||||
|
pdfGenerator pdf.IPDFGenerator
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServices(
|
func NewServices(
|
||||||
repository *Repository,
|
dbService db.IDBService,
|
||||||
storyService *story_service.StoryService,
|
storyService *story_service.StoryService,
|
||||||
|
linkService link.ILinkService,
|
||||||
|
passwordGenerator password.IPasswordGenerator,
|
||||||
|
pdfGenerator pdf.IPDFGenerator,
|
||||||
) *Services {
|
) *Services {
|
||||||
return &Services{
|
return &Services{
|
||||||
repository: repository,
|
dbService: dbService,
|
||||||
storyService: storyService,
|
storyService: storyService,
|
||||||
|
linkService: linkService,
|
||||||
|
passwordGenerator: passwordGenerator,
|
||||||
|
pdfGenerator: pdfGenerator,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Services) GiveApplications(ctx context.Context, req *proto.GiveApplicationsReq) (*proto.GiveApplicationsRsp, error) {
|
func (s *Services) GiveApplications(ctx context.Context, req *proto.GiveApplicationsReq) (*proto.GiveApplicationsRsp, error) {
|
||||||
applications := mapProtoApplicationsToApplications(req.Applications)
|
applications := mapProtoApplicationsToApplications(req.Applications)
|
||||||
if err := s.repository.GiveApplications(ctx, req.TeamId, applications); err != nil {
|
if err := s.dbService.GiveApplications(ctx, req.TeamId, applications); err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, err.Error())
|
return nil, status.Errorf(codes.Internal, err.Error())
|
||||||
}
|
}
|
||||||
return &proto.GiveApplicationsRsp{}, nil
|
return &proto.GiveApplicationsRsp{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Services) GameStop(ctx context.Context, req *proto.GameStopReq) (*proto.GameStopRsp, error) {
|
func (s *Services) GetGame(ctx context.Context, _ *proto.GetGameReq) (*proto.GetGameRsp, error) {
|
||||||
panic("unimplemented")
|
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, req *proto.GameStartReq) (*proto.GameStartRsp, error) {
|
func (s *Services) GameStart(ctx context.Context, _ *proto.GameStartReq) (*proto.GameStartRsp, error) {
|
||||||
panic("unimplemented")
|
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) {
|
func (s *Services) AddAction(ctx context.Context, req *proto.AddActionReq) (*proto.AddActionRsp, error) {
|
||||||
@@ -61,13 +90,33 @@ func (s *Services) AddAction(ctx context.Context, req *proto.AddActionReq) (*pro
|
|||||||
Applications: mapStoryApplicationsToApplications(place.Applications),
|
Applications: mapStoryApplicationsToApplications(place.Applications),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err := s.repository.AddActions(ctx, team.ID, actions); err != nil {
|
if err := s.dbService.AddActions(ctx, team.ID, actions); err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, err.Error())
|
return nil, status.Errorf(codes.Internal, err.Error())
|
||||||
}
|
}
|
||||||
if err := s.repository.AddApplications(ctx, actions); err != nil {
|
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())
|
return nil, status.Errorf(codes.Internal, err.Error())
|
||||||
}
|
}
|
||||||
log(team, "add action", actions)
|
addLog(team, "add action", actions)
|
||||||
return &proto.AddActionRsp{}, nil
|
return &proto.AddActionRsp{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,20 +126,31 @@ func (s *Services) GetTeam(ctx context.Context, req *proto.GetTeamReq) (*proto.G
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
actions, err := s.repository.GetActions(ctx, team.ID)
|
actions, err := s.dbService.GetActions(ctx, team.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, err.Error())
|
return nil, status.Errorf(codes.Internal, err.Error())
|
||||||
}
|
}
|
||||||
res := make([]*proto.Action, 0, len(actions))
|
actionsCodes := make([]string, 0, len(actions))
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
newAction := mapActionToProtoAction(action)
|
actionsCodes = append(actionsCodes, action.Place)
|
||||||
place := s.storyService.GetPlace(action.Place)
|
}
|
||||||
newAction.Text = place.Text
|
|
||||||
|
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.Name = place.Name
|
||||||
|
newAction.Text = place.Text
|
||||||
|
newAction.Image = place.Image
|
||||||
newAction.Applications = make([]*proto.Application, 0, len(place.Applications))
|
newAction.Applications = make([]*proto.Application, 0, len(place.Applications))
|
||||||
for _, application := range place.Applications {
|
for _, application := range place.Applications {
|
||||||
newAction.Applications = append(newAction.Applications, mapStoryApplicationToProtoApplication(application))
|
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)
|
res = append(res, newAction)
|
||||||
}
|
}
|
||||||
return &proto.GetTeamRsp{
|
return &proto.GetTeamRsp{
|
||||||
@@ -104,27 +164,24 @@ func (s *Services) GetTeamsCSV(ctx context.Context, req *proto.GetTeamsCSVReq) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Services) GetTeams(ctx context.Context, _ *proto.GetTeamsReq) (*proto.GetTeamsRsp, error) {
|
func (s *Services) GetTeams(ctx context.Context, _ *proto.GetTeamsReq) (*proto.GetTeamsRsp, error) {
|
||||||
teams, err := s.repository.GetTeams(ctx)
|
teams, err := s.dbService.GetTeams(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, err.Error())
|
return nil, status.Errorf(codes.Internal, err.Error())
|
||||||
}
|
}
|
||||||
res := make([]*proto.TeamAdvanced, 0, len(teams))
|
res := make([]*proto.TeamAdvanced, 0, len(teams))
|
||||||
for _, team := range teams {
|
for _, team := range teams {
|
||||||
newTeam := mapTeamsToTeamAdvanced(team)
|
newTeam := mapTeamsToTeamAdvanced(team)
|
||||||
actions, err := s.repository.GetActions(ctx, team.ID)
|
actions, err := s.dbService.GetActions(ctx, team.ID)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
newTeam.Url, err = getTeamUrl(team)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
newTeam.Url = s.linkService.GetTeamClientLink(team.Name, team.Password)
|
||||||
newTeam.SpendTime = int64(len(actions))
|
newTeam.SpendTime = int64(len(actions))
|
||||||
applications, err := s.repository.GetApplications(ctx, team.ID, "NEW")
|
currentApplications, err := s.dbService.GetApplicationsByState(ctx, team.ID, "NEW")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
newTeam.Applications = mapApplicationsToProtoApplications(applications)
|
newTeam.Applications = mapApplicationsToProtoApplications(currentApplications)
|
||||||
res = append(res, newTeam)
|
res = append(res, newTeam)
|
||||||
}
|
}
|
||||||
return &proto.GetTeamsRsp{Teams: res}, err
|
return &proto.GetTeamsRsp{Teams: res}, err
|
||||||
@@ -134,10 +191,10 @@ func (s *Services) AddTeams(ctx context.Context, req *proto.AddTeamsReq) (*proto
|
|||||||
inTeams := make([]*models.Team, 0, len(req.Teams))
|
inTeams := make([]*models.Team, 0, len(req.Teams))
|
||||||
for _, team := range req.Teams {
|
for _, team := range req.Teams {
|
||||||
t := mapProtoTeamsToTeam(team)
|
t := mapProtoTeamsToTeam(team)
|
||||||
t.Password = password.GenPass(8)
|
t.Password = s.passwordGenerator.GeneratePassword(8)
|
||||||
inTeams = append(inTeams, t)
|
inTeams = append(inTeams, t)
|
||||||
}
|
}
|
||||||
teams, err := s.repository.AddTeams(ctx, inTeams)
|
teams, err := s.dbService.AddTeams(ctx, inTeams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, err.Error())
|
return nil, status.Errorf(codes.Internal, err.Error())
|
||||||
}
|
}
|
||||||
@@ -145,7 +202,111 @@ func (s *Services) AddTeams(ctx context.Context, req *proto.AddTeamsReq) (*proto
|
|||||||
for _, team := range teams {
|
for _, team := range teams {
|
||||||
res = append(res, mapTeamsToTeamFull(team))
|
res = append(res, mapTeamsToTeamFull(team))
|
||||||
}
|
}
|
||||||
return &proto.AddTeamsRsp{Teams: res}, err
|
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) {
|
func (s *Services) getTeam(ctx context.Context) (*models.Team, error) {
|
||||||
@@ -169,52 +330,16 @@ func (s *Services) getTeam(ctx context.Context) (*models.Team, error) {
|
|||||||
}
|
}
|
||||||
password := passwordArr[0]
|
password := passwordArr[0]
|
||||||
|
|
||||||
team, err := s.repository.GetTeam(ctx, teamId, password)
|
team, err := s.dbService.GetTeam(ctx, teamId, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Unauthenticated, err.Error())
|
return nil, status.Errorf(codes.Unauthenticated, err.Error())
|
||||||
}
|
}
|
||||||
return team, nil
|
return team, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func log(team *models.Team, action string, v any) {
|
func addLog(team *models.Team, action string, v any) {
|
||||||
vJson, err := json.Marshal(v)
|
vJson, err := json.Marshal(v)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Printf("Team %s: %s %s\n", team.Name, action, string(vJson))
|
fmt.Printf("Team %s: %s %s\n", team.Name, action, string(vJson))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTeamUrl(team *models.Team) (string, error) {
|
|
||||||
ips, err := getLocalIPs()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
ip := ips[0]
|
|
||||||
u := fmt.Sprintf("http://%s:8100?name=%s&password=%s", ip, url.PathEscape(team.Name), team.Password)
|
|
||||||
return u, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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,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"`
|
||||||
|
}
|
||||||
@@ -1,79 +1,318 @@
|
|||||||
package story_service
|
package story_service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"context"
|
||||||
"os"
|
"evening_detective/internal/modules/cleaner"
|
||||||
|
"evening_detective/internal/modules/formatter"
|
||||||
|
"evening_detective/internal/services/link"
|
||||||
|
"evening_detective/internal/services/story_service/models"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
replaceMap = map[string]string{
|
|
||||||
"a": "а",
|
|
||||||
"e": "е",
|
|
||||||
"o": "о",
|
|
||||||
"c": "с",
|
|
||||||
"p": "р",
|
|
||||||
"x": "х",
|
|
||||||
"y": "у",
|
|
||||||
"k": "к",
|
|
||||||
"m": "м",
|
|
||||||
"t": "т",
|
|
||||||
"h": "н",
|
|
||||||
"b": "в",
|
|
||||||
"u": "и",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type Story struct {
|
|
||||||
Places []*Place `json:"places"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Place struct {
|
|
||||||
Code string `json:"code"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
Applications []*Application `json:"applications"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Application struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type StoryService struct {
|
type StoryService struct {
|
||||||
story *Story
|
cleaner cleaner.ICleaner
|
||||||
|
formatter formatter.IFormatter
|
||||||
|
story *models.Story
|
||||||
|
storyStorage IStoryStorage
|
||||||
|
linkService link.ILinkService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStoryService() (*StoryService, error) {
|
func NewStoryService(
|
||||||
data, err := os.ReadFile("./story/story.json")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
story := &Story{}
|
s.story = story
|
||||||
if err := json.Unmarshal(data, story); err != nil {
|
return s, nil
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &StoryService{story: story}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StoryService) GetPlace(code string) *Place {
|
func (s *StoryService) Update(ctx context.Context) error {
|
||||||
code = clearCode(code)
|
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 {
|
for _, place := range s.story.Places {
|
||||||
if clearCode(place.Code) == code {
|
if s.cleaner.ClearCode(place.Code) == clearCode {
|
||||||
return place
|
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 &Place{
|
return models.NewNotFoundPlace(code)
|
||||||
Code: code,
|
}
|
||||||
Name: "Не найдено",
|
|
||||||
Text: "Такой точки не существует.",
|
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 clearCode(code string) string {
|
func (s *StoryService) findPlaceLinksInText(text string) []string {
|
||||||
code = strings.ToLower(code)
|
re := regexp.MustCompile(`\(\[[a-zA-Zа-яА-Я\d-]+\]\)`)
|
||||||
code = strings.ReplaceAll(code, "-", "")
|
return re.FindAllString(text, -1)
|
||||||
for latin, cyrillic := range replaceMap {
|
|
||||||
code = strings.ReplaceAll(code, latin, cyrillic)
|
|
||||||
}
|
|
||||||
return code
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -33,6 +33,28 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/game": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "EveningDetective_GetGame",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/evening_detectiveGetGameRsp"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/runtimeError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"EveningDetective"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/game/start": {
|
"/game/start": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "EveningDetective_GameStart",
|
"operationId": "EveningDetective_GameStart",
|
||||||
@@ -97,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": {
|
"/ping": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "EveningDetective_Ping",
|
"operationId": "EveningDetective_Ping",
|
||||||
@@ -225,6 +301,28 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/teams/pdf": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "EveningDetective_DownloadTeamsQrCodesFile",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/evening_detectiveDownloadTeamsQrCodesFileRsp"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/runtimeError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"EveningDetective"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/teams/{teamId}/applications": {
|
"/teams/{teamId}/applications": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "EveningDetective_GiveApplications",
|
"operationId": "EveningDetective_GiveApplications",
|
||||||
@@ -266,6 +364,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
"GetGraphRspEdge": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"from": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"to": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"arrows": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"evening_detectiveAction": {
|
"evening_detectiveAction": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -282,11 +397,23 @@
|
|||||||
"text": {
|
"text": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"image": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"applications": {
|
"applications": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/evening_detectiveApplication"
|
"$ref": "#/definitions/evening_detectiveApplication"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"hidden": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"doors": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/evening_detectiveDoor"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -335,6 +462,32 @@
|
|||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"number": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"evening_detectiveDoor": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"show": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"evening_detectiveDownloadTeamsQrCodesFileRsp": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"result": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "byte"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -356,6 +509,45 @@
|
|||||||
"evening_detectiveGameStopRsp": {
|
"evening_detectiveGameStopRsp": {
|
||||||
"type": "object"
|
"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": {
|
"evening_detectiveGetTeamRsp": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -407,6 +599,60 @@
|
|||||||
"evening_detectiveGiveApplicationsRsp": {
|
"evening_detectiveGiveApplicationsRsp": {
|
||||||
"type": "object"
|
"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": {
|
"evening_detectivePingRsp": {
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
@@ -461,6 +707,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"evening_detectiveUpdateNodeReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"node": {
|
||||||
|
"$ref": "#/definitions/evening_detectiveGraphNode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"evening_detectiveUpdateNodeRsp": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"protobufAny": {
|
"protobufAny": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.3.0
|
// - protoc-gen-go-grpc v1.5.1
|
||||||
// - protoc v5.26.1
|
// - protoc v6.32.1
|
||||||
// source: main.proto
|
// source: main.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
@@ -15,8 +15,8 @@ import (
|
|||||||
|
|
||||||
// This is a compile-time assertion to ensure that this generated file
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
// is compatible with the grpc package it is being compiled against.
|
// is compatible with the grpc package it is being compiled against.
|
||||||
// Requires gRPC-Go v1.32.0 or later.
|
// Requires gRPC-Go v1.64.0 or later.
|
||||||
const _ = grpc.SupportPackageIsVersion7
|
const _ = grpc.SupportPackageIsVersion9
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EveningDetective_Ping_FullMethodName = "/crabs.evening_detective.EveningDetective/Ping"
|
EveningDetective_Ping_FullMethodName = "/crabs.evening_detective.EveningDetective/Ping"
|
||||||
@@ -25,9 +25,13 @@ const (
|
|||||||
EveningDetective_GetTeamsCSV_FullMethodName = "/crabs.evening_detective.EveningDetective/GetTeamsCSV"
|
EveningDetective_GetTeamsCSV_FullMethodName = "/crabs.evening_detective.EveningDetective/GetTeamsCSV"
|
||||||
EveningDetective_GetTeam_FullMethodName = "/crabs.evening_detective.EveningDetective/GetTeam"
|
EveningDetective_GetTeam_FullMethodName = "/crabs.evening_detective.EveningDetective/GetTeam"
|
||||||
EveningDetective_AddAction_FullMethodName = "/crabs.evening_detective.EveningDetective/AddAction"
|
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_GameStart_FullMethodName = "/crabs.evening_detective.EveningDetective/GameStart"
|
||||||
EveningDetective_GameStop_FullMethodName = "/crabs.evening_detective.EveningDetective/GameStop"
|
EveningDetective_GameStop_FullMethodName = "/crabs.evening_detective.EveningDetective/GameStop"
|
||||||
EveningDetective_GiveApplications_FullMethodName = "/crabs.evening_detective.EveningDetective/GiveApplications"
|
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.
|
// EveningDetectiveClient is the client API for EveningDetective service.
|
||||||
@@ -40,9 +44,13 @@ type EveningDetectiveClient interface {
|
|||||||
GetTeamsCSV(ctx context.Context, in *GetTeamsCSVReq, opts ...grpc.CallOption) (*GetTeamsCSVRsp, error)
|
GetTeamsCSV(ctx context.Context, in *GetTeamsCSVReq, opts ...grpc.CallOption) (*GetTeamsCSVRsp, error)
|
||||||
GetTeam(ctx context.Context, in *GetTeamReq, opts ...grpc.CallOption) (*GetTeamRsp, error)
|
GetTeam(ctx context.Context, in *GetTeamReq, opts ...grpc.CallOption) (*GetTeamRsp, error)
|
||||||
AddAction(ctx context.Context, in *AddActionReq, opts ...grpc.CallOption) (*AddActionRsp, 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)
|
GameStart(ctx context.Context, in *GameStartReq, opts ...grpc.CallOption) (*GameStartRsp, error)
|
||||||
GameStop(ctx context.Context, in *GameStopReq, opts ...grpc.CallOption) (*GameStopRsp, error)
|
GameStop(ctx context.Context, in *GameStopReq, opts ...grpc.CallOption) (*GameStopRsp, error)
|
||||||
GiveApplications(ctx context.Context, in *GiveApplicationsReq, opts ...grpc.CallOption) (*GiveApplicationsRsp, 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 {
|
type eveningDetectiveClient struct {
|
||||||
@@ -54,8 +62,9 @@ func NewEveningDetectiveClient(cc grpc.ClientConnInterface) EveningDetectiveClie
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *eveningDetectiveClient) Ping(ctx context.Context, in *PingReq, opts ...grpc.CallOption) (*PingRsp, error) {
|
func (c *eveningDetectiveClient) Ping(ctx context.Context, in *PingReq, opts ...grpc.CallOption) (*PingRsp, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(PingRsp)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -63,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) {
|
func (c *eveningDetectiveClient) AddTeams(ctx context.Context, in *AddTeamsReq, opts ...grpc.CallOption) (*AddTeamsRsp, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(AddTeamsRsp)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -72,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) {
|
func (c *eveningDetectiveClient) GetTeams(ctx context.Context, in *GetTeamsReq, opts ...grpc.CallOption) (*GetTeamsRsp, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(GetTeamsRsp)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -81,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) {
|
func (c *eveningDetectiveClient) GetTeamsCSV(ctx context.Context, in *GetTeamsCSVReq, opts ...grpc.CallOption) (*GetTeamsCSVRsp, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(GetTeamsCSVRsp)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -90,8 +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) {
|
func (c *eveningDetectiveClient) GetTeam(ctx context.Context, in *GetTeamReq, opts ...grpc.CallOption) (*GetTeamRsp, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(GetTeamRsp)
|
out := new(GetTeamRsp)
|
||||||
err := c.cc.Invoke(ctx, EveningDetective_GetTeam_FullMethodName, in, out, opts...)
|
err := c.cc.Invoke(ctx, EveningDetective_GetTeam_FullMethodName, in, out, cOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -99,8 +112,19 @@ func (c *eveningDetectiveClient) GetTeam(ctx context.Context, in *GetTeamReq, op
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *eveningDetectiveClient) AddAction(ctx context.Context, in *AddActionReq, opts ...grpc.CallOption) (*AddActionRsp, error) {
|
func (c *eveningDetectiveClient) AddAction(ctx context.Context, in *AddActionReq, opts ...grpc.CallOption) (*AddActionRsp, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(AddActionRsp)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -108,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) {
|
func (c *eveningDetectiveClient) GameStart(ctx context.Context, in *GameStartReq, opts ...grpc.CallOption) (*GameStartRsp, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(GameStartRsp)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -117,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) {
|
func (c *eveningDetectiveClient) GameStop(ctx context.Context, in *GameStopReq, opts ...grpc.CallOption) (*GameStopRsp, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(GameStopRsp)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -126,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) {
|
func (c *eveningDetectiveClient) GiveApplications(ctx context.Context, in *GiveApplicationsReq, opts ...grpc.CallOption) (*GiveApplicationsRsp, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(GiveApplicationsRsp)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -136,7 +193,7 @@ func (c *eveningDetectiveClient) GiveApplications(ctx context.Context, in *GiveA
|
|||||||
|
|
||||||
// EveningDetectiveServer is the server API for EveningDetective service.
|
// EveningDetectiveServer is the server API for EveningDetective service.
|
||||||
// All implementations must embed UnimplementedEveningDetectiveServer
|
// All implementations must embed UnimplementedEveningDetectiveServer
|
||||||
// for forward compatibility
|
// for forward compatibility.
|
||||||
type EveningDetectiveServer interface {
|
type EveningDetectiveServer interface {
|
||||||
Ping(context.Context, *PingReq) (*PingRsp, error)
|
Ping(context.Context, *PingReq) (*PingRsp, error)
|
||||||
AddTeams(context.Context, *AddTeamsReq) (*AddTeamsRsp, error)
|
AddTeams(context.Context, *AddTeamsReq) (*AddTeamsRsp, error)
|
||||||
@@ -144,15 +201,22 @@ type EveningDetectiveServer interface {
|
|||||||
GetTeamsCSV(context.Context, *GetTeamsCSVReq) (*GetTeamsCSVRsp, error)
|
GetTeamsCSV(context.Context, *GetTeamsCSVReq) (*GetTeamsCSVRsp, error)
|
||||||
GetTeam(context.Context, *GetTeamReq) (*GetTeamRsp, error)
|
GetTeam(context.Context, *GetTeamReq) (*GetTeamRsp, error)
|
||||||
AddAction(context.Context, *AddActionReq) (*AddActionRsp, error)
|
AddAction(context.Context, *AddActionReq) (*AddActionRsp, error)
|
||||||
|
GetGame(context.Context, *GetGameReq) (*GetGameRsp, error)
|
||||||
GameStart(context.Context, *GameStartReq) (*GameStartRsp, error)
|
GameStart(context.Context, *GameStartReq) (*GameStartRsp, error)
|
||||||
GameStop(context.Context, *GameStopReq) (*GameStopRsp, error)
|
GameStop(context.Context, *GameStopReq) (*GameStopRsp, error)
|
||||||
GiveApplications(context.Context, *GiveApplicationsReq) (*GiveApplicationsRsp, 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()
|
mustEmbedUnimplementedEveningDetectiveServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnimplementedEveningDetectiveServer must be embedded to have forward compatible implementations.
|
// UnimplementedEveningDetectiveServer must be embedded to have
|
||||||
type UnimplementedEveningDetectiveServer struct {
|
// 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) {
|
func (UnimplementedEveningDetectiveServer) Ping(context.Context, *PingReq) (*PingRsp, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented")
|
||||||
@@ -172,6 +236,9 @@ func (UnimplementedEveningDetectiveServer) GetTeam(context.Context, *GetTeamReq)
|
|||||||
func (UnimplementedEveningDetectiveServer) AddAction(context.Context, *AddActionReq) (*AddActionRsp, error) {
|
func (UnimplementedEveningDetectiveServer) AddAction(context.Context, *AddActionReq) (*AddActionRsp, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method AddAction not implemented")
|
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) {
|
func (UnimplementedEveningDetectiveServer) GameStart(context.Context, *GameStartReq) (*GameStartRsp, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GameStart not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GameStart not implemented")
|
||||||
}
|
}
|
||||||
@@ -181,7 +248,17 @@ func (UnimplementedEveningDetectiveServer) GameStop(context.Context, *GameStopRe
|
|||||||
func (UnimplementedEveningDetectiveServer) GiveApplications(context.Context, *GiveApplicationsReq) (*GiveApplicationsRsp, error) {
|
func (UnimplementedEveningDetectiveServer) GiveApplications(context.Context, *GiveApplicationsReq) (*GiveApplicationsRsp, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GiveApplications not implemented")
|
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) mustEmbedUnimplementedEveningDetectiveServer() {}
|
||||||
|
func (UnimplementedEveningDetectiveServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
// UnsafeEveningDetectiveServer may be embedded to opt out of forward compatibility for this service.
|
// 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
|
// Use of this interface is not recommended, as added methods to EveningDetectiveServer will
|
||||||
@@ -191,6 +268,13 @@ type UnsafeEveningDetectiveServer interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RegisterEveningDetectiveServer(s grpc.ServiceRegistrar, srv EveningDetectiveServer) {
|
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)
|
s.RegisterService(&EveningDetective_ServiceDesc, srv)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,6 +386,24 @@ func _EveningDetective_AddAction_Handler(srv interface{}, ctx context.Context, d
|
|||||||
return interceptor(ctx, in, info, handler)
|
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) {
|
func _EveningDetective_GameStart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(GameStartReq)
|
in := new(GameStartReq)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@@ -356,6 +458,60 @@ func _EveningDetective_GiveApplications_Handler(srv interface{}, ctx context.Con
|
|||||||
return interceptor(ctx, in, info, handler)
|
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.
|
// EveningDetective_ServiceDesc is the grpc.ServiceDesc for EveningDetective service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -387,6 +543,10 @@ var EveningDetective_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "AddAction",
|
MethodName: "AddAction",
|
||||||
Handler: _EveningDetective_AddAction_Handler,
|
Handler: _EveningDetective_AddAction_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetGame",
|
||||||
|
Handler: _EveningDetective_GetGame_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "GameStart",
|
MethodName: "GameStart",
|
||||||
Handler: _EveningDetective_GameStart_Handler,
|
Handler: _EveningDetective_GameStart_Handler,
|
||||||
@@ -399,6 +559,18 @@ var EveningDetective_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "GiveApplications",
|
MethodName: "GiveApplications",
|
||||||
Handler: _EveningDetective_GiveApplications_Handler,
|
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{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "main.proto",
|
Metadata: "main.proto",
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
import{_ as o,c as s,a as t,o as a}from"./index-DNz9l3r8.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);--main-color: rgba(115, 185, 83, 1);--second-color: rgba(98, 156, 68, 1);--main-back-color: rgba(240, 240, 240, 1);--main-back-item-color: rgba(254, 254, 254, 1)}: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}*,*:before,*:after{box-sizing:border-box;margin:0;font-weight:400}body{min-height:100dvh;color:var(--color-text);background:var(--main-back-color);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}.header-block{height:50px;background-color:var(--main-color);font-size:large;color:#fff;vertical-align:middle;padding:10px 0 10px 16px;font-weight:700}.input-custom{width:100%;box-sizing:border-box;margin-bottom:15px}.button-custom{margin-left:auto;background-color:var(--main-color);font-weight:600;color:#fff}.button-custom:hover{background-color:var(--second-color)}.input-custom,.button-custom{padding:12px 16px;border:1px solid #ddd;border-radius:15px;font-size:16px}.button-container{display:flex}.center-message{display:flex;justify-content:center;align-items:center;height:calc(100dvh - 100px);text-align:center}header[data-v-e5db0c22]{line-height:1.5;max-height:100vh}.logo[data-v-e5db0c22]{display:block;margin:0 auto 2rem}nav[data-v-e5db0c22]{width:100%;font-size:12px;text-align:center;margin-top:2rem}nav a.router-link-exact-active[data-v-e5db0c22]{color:var(--color-text)}nav a.router-link-exact-active[data-v-e5db0c22]:hover{background-color:transparent}nav a[data-v-e5db0c22]{display:inline-block;padding:0 1rem;border-left:1px solid var(--color-border)}nav a[data-v-e5db0c22]:first-of-type{border:0}@media (min-width: 1024px){header[data-v-e5db0c22]{display:flex;place-items:center;padding-right:calc(var(--section-gap) / 2)}.logo[data-v-e5db0c22]{margin:0 2rem 0 0}header .wrapper[data-v-e5db0c22]{display:flex;place-items:flex-start;flex-wrap:wrap}nav[data-v-e5db0c22]{text-align:left;margin-left:-1rem;font-size:1rem;padding:1rem 0;margin-top:1rem}}body[data-v-a696cd7e]{font-family:Arial,sans-serif;margin:20px}table[data-v-a696cd7e]{width:700px;border-collapse:collapse;margin:30px auto;border:1px solid #444444}th[data-v-a696cd7e],td[data-v-a696cd7e]{padding:12px;text-align:left}th[data-v-a696cd7e]{background-color:var(--main-color);color:#fff;font-weight:700}tr[data-v-a696cd7e]:nth-child(odd){background-color:#e9f0e6}tr[data-v-a696cd7e]:nth-child(2n){background-color:#fff}tr[data-v-a696cd7e]:hover{background-color:#cfcfcf}.time[data-v-a696cd7e]{white-space:nowrap}.team-name[data-v-a696cd7e]{font-weight:600}.link-button[data-v-a696cd7e]{display:inline;border:none;background:none;padding:0;margin:0;font:inherit;cursor:pointer;color:var(--main-color);text-decoration:underline;font-weight:600;-webkit-appearance:none;-moz-appearance:none;appearance:none;line-height:inherit;text-align:left}.link-button[data-v-a696cd7e]:hover{color:var(--second-color);text-decoration:none}.link-button[data-v-a696cd7e]:active{color:#036}.link-button[data-v-a696cd7e]:focus{outline:none;text-decoration:none;box-shadow:0 0 0 2px #0066cc4d}.form-block[data-v-a696cd7e]{width:700px;margin:0 auto}a[data-v-a696cd7e]{color:var(--second-color);text-decoration:none;transition:all .2s ease;cursor:pointer}a[data-v-a696cd7e]:hover{text-decoration:underline;text-decoration-thickness:2px;text-underline-offset:3px}a[data-v-a696cd7e]:focus-visible{outline:2px solid #3182ce;outline-offset:2px;border-radius:2px}a[disabled][data-v-a696cd7e]{color:#a0aec0;pointer-events:none;cursor:not-allowed}.qr[data-v-a696cd7e]{position:absolute;top:80px;right:30px;text-align:center;width:120px}.button-container[data-v-a696cd7e]{margin-bottom:30px}
|
|
||||||
@@ -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-DuNBa2Ey.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);--main-color: rgba(34, 50, 60, 1);--second-color: rgb(136, 105, 31);--main-back-color: rgba(240, 240, 240, 1);--main-back-item-color: rgba(254, 254, 254, 1)}: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}*,*:before,*:after{box-sizing:border-box;margin:0;font-weight:400}body{min-height:100dvh;color:var(--color-text);background:var(--main-back-color);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}.header-block{height:60px;background-color:var(--main-color);font-size:large;color:#fff;vertical-align:middle;padding:15px 0 10px 16px;font-weight:700}.input-custom{width:100%;box-sizing:border-box;margin-bottom:15px}.button-custom{margin-left:auto;background-color:var(--main-color);font-weight:600;color:#fff}.button-custom:hover{background-color:var(--main-color);opacity:.9}.input-custom,.button-custom{padding:12px 16px;border:1px solid #ddd;border-radius:15px;font-size:16px}.button-container{display:flex}.center-message{display:flex;justify-content:center;align-items:center;height:calc(100dvh - 100px);text-align:center}header[data-v-913ef6b1]{line-height:1.5;max-height:100vh}.logo[data-v-913ef6b1]{display:block;margin:0 auto 2rem}nav[data-v-913ef6b1]{width:100%;font-size:12px;text-align:center;margin-top:2rem}nav a.router-link-exact-active[data-v-913ef6b1]{color:var(--color-text)}nav a.router-link-exact-active[data-v-913ef6b1]:hover{background-color:transparent}nav a[data-v-913ef6b1]{display:inline-block;padding:0 1rem;border-left:1px solid var(--color-border)}nav a[data-v-913ef6b1]:first-of-type{border:0}@media (min-width: 1024px){header[data-v-913ef6b1]{display:flex;place-items:center;padding-right:calc(var(--section-gap) / 2)}.logo[data-v-913ef6b1]{margin:0 2rem 0 0}header .wrapper[data-v-913ef6b1]{display:flex;place-items:flex-start;flex-wrap:wrap}nav[data-v-913ef6b1]{text-align:left;margin-left:-1rem;font-size:1rem;padding:1rem 0;margin-top:1rem}}body[data-v-e4c8675b]{overflow:hidden}.hr[data-v-e4c8675b]{margin:7px 0}.body-custom[data-v-e4c8675b]{font-size:medium}.logo[data-v-e4c8675b]{float:left;margin:10px}.form-custom[data-v-e4c8675b]{border:1px solid #444444;background-color:var(--main-back-color);position:fixed;bottom:0;left:0;width:100%;padding:20px;color:#fff}.message-cloud[data-v-e4c8675b]{border:1px solid #444444;border-radius:15px;margin:12px 2px;padding:16px;background-color:var(--main-back-item-color)}.message-header[data-v-e4c8675b]{font-size:large;font-weight:200}.message-content[data-v-e4c8675b]{font-weight:500;white-space:pre-line}.message-footer[data-v-e4c8675b]{font-weight:400;color:var(--second-color)}.form-block[data-v-e4c8675b]{height:140px}.messages-block[data-v-e4c8675b]{height:calc(100dvh - 190px);overflow-y:auto;scrollbar-width:none}@media (min-width: 1025px){.center-block-custom[data-v-e4c8675b]{width:700px;margin:0 auto}}.center-message[data-v-e4c8675b]{height:calc(100dvh - 140px)}.error-message[data-v-13746d20]{color:brown;margin:16px 0}
|
|
||||||
|
Before Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,159 +0,0 @@
|
|||||||
{
|
|
||||||
"places": [
|
|
||||||
{
|
|
||||||
"code": "ВД",
|
|
||||||
"name": "Вечерний детектив",
|
|
||||||
"text": "Дело №1 “Последний костёр”\nАвторы: Фёдоров Владимир, Лисовая Дарья"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "А",
|
|
||||||
"name": "Администрация",
|
|
||||||
"text": "Тут работают директор и старший вожатый, на столе вы находите расписание на 23 августа. Стопку книг по педагогике и какие-то записки от детей, похоже они очень любили Лёху.",
|
|
||||||
"applications": [
|
|
||||||
{
|
|
||||||
"name": "Расписание дня"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "В-1",
|
|
||||||
"name": "Вход",
|
|
||||||
"text": "Ржавые ворота с выцветшей табличкой «Добро пожаловать в «Сосновый Бор» скрипят на ветру. За ними — узкая дорога, уходящая вглубь соснового леса. На покосившемся стенде у проходной — пожелтевший плакат с информацией:\n\n\"Лагерь «Сосновый Бор» - Место, где рождаются характеры.\"\n\n«Орлы» — спортивные, загорелые, с грамотами за победы в эстафетах. Их крики слышны даже на рассвете.\n\n«Лисы» — те, кто вместо костра сидит с книгами. Их шепотом называют «ботанами», но именно они всегда знают ответ.\n\n«Волки» — вечные нарушители. Их следы находят то на крыше столовой, то у запретной водонапорной вышки.\n\n«Совы» — тихие художники и поэты. Их рисунки иногда находят в лесу — странные, будто нарисованные не совсем их рукой.\n\nВ самом низу подпись: Директор лагеря - Виктор Сергеевич Громов."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "В-2",
|
|
||||||
"name": "Водонапорная вышка",
|
|
||||||
"text": "Труп лежит на земле с разбитой головой, рядом лежит окровавленный камень, судя по всему от него и погиб старший вожатый. Рядом с трупом вы видите отпечатки кроссовок свежие, глубокие, будто кто-то бежал или резко разворачивался. Подошва – с характерным рисунком в виде зигзагов. И еще следы двух пар ботинок: Первые – массивные, с грубым протектором. Следы ведут к телу, затем резко обрываются – будто человек замер на месте. Вторые – аккуратные, с узким носком. Они подходят к камню, а затем удаляются в сторону вышки."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "Д",
|
|
||||||
"name": "Душ",
|
|
||||||
"text": "Вы дергаете дверь душа, она закрыта, завхоз на перекуре, говорит что душ не работает, у качегара голова болит, уже вторые сутки, они громко смеются. Поговорив с женщинами вы узнаете что кормят в лагере очень плохо, даже 2 проверки приезжали, нарушений не нашли, но дети жалуются и почти не едят, Лёша сам ругаться приходил несколько раз, уж очень он за детей волновался."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "К-1",
|
|
||||||
"name": "Клуб",
|
|
||||||
"text": "В клубе вас встречает диджей Пётр, рассказывает что дискотека прошла на ура, танцевали и пели под все самые лучшие песни. Кажется он почти не общался с Лехой и до сих пор не знает что произошло. А какие медляки, танцевал весь лагерь, правда Макса и Даши не было, они у нас главные знаменитости, танцуют медляки каждый вечер а днем делают вид что противны друг другу, думают что дети верят в их притворство."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "К-2",
|
|
||||||
"name": "Костровище",
|
|
||||||
"text": "В глубине лагеря, за последним отрядом, расчищена круглая площадка, окруженная полукругом пеньков-сидушек, сколоченных из толстых спилов сосны. В центре — огромный костровой круг, выложенный из камней, почерневших от бесчисленных огней."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "М",
|
|
||||||
"name": "Медпункт",
|
|
||||||
"text": "Небольшое побелено-голубое здание в тени сосен. Внутри – приемная с выцветшими плакатами про \"чистые руки\" и \"опасность клещей\", изолятор с двумя койками за занавеской, а дальше – общий душ и туалет. На столе стоят 3 кружки из под чая. Печенье \"Юбилейное\" в открытой пачке. Медицинская карта с последней записью: \"24.08.99 – Волков С. (отряд \"Орлы\") – жалобы на температуру и тошноту. Диагноз: пищевое отравление?\" В мусорном ведре вы замечаете упаковку от таблетки, 3 пакетика чая и использованный презерватив. Слабый аромат духов – дешевый, сладкий, явно не медицинский."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "О-1",
|
|
||||||
"name": "Отряд 1",
|
|
||||||
"text": "Белое кирпичное здание, с выложенными кирпичом \"1970\". Сбоку нарисован Чебурашка, коричневой и красной краской. Отряд опрятный но сильно пахнет потом. Койки заправлены с армейской аккуратностью. На стене – газета с детскими стихами, где кто-то красной ручкой исправил рифмы на похабные."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "О-2",
|
|
||||||
"name": "Отряд 2",
|
|
||||||
"text": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "О-3",
|
|
||||||
"name": "Отряд 3",
|
|
||||||
"text": "Приближаясь, вы осматриваете кирпичное здание, с нарисованным сбоку здания мультяшным героем. Переводя взгляд в окно, вы видите детей, разбившись на группки, кто то рисует, кто то бегает, а кто то просто сидит в сторонке. Вы тоже за тем, что пропало? — раздаётся голос за спиной. Обернувшись, видите мальчика лет 12 с слишком взрослым взглядом. Лёха говорит, что если что — искать надо в \"лисах\". Только он не договорил... что именно. Он нервно оглядывается и исчезает за углом, оставив вас с новой загадкой и ощущением, что за вами уже наблюдают."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "О-4",
|
|
||||||
"name": "Отряд 4",
|
|
||||||
"text": "У входа в отряд вы десятки пар обуви, аккуратно выставленных в ряд. Среди них вы сразу замечаете те самые кроссовки — с характерным зигзагообразным протектором, слегка запачканные грязью и... чем-то тёмным у носка. — Это Катины! — оживляется девочка с косичками, тыча пальцем в обувь. — Она их всегда носит, даже когда дождь!"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "П",
|
|
||||||
"name": "Площадь",
|
|
||||||
"text": "Площадь в лагере, развивается флаг России, флаг лагеря, зелёное полотно с белой сосной. Чисто выметен асфальт, музыку здесь почти не слышно хотя граммофон висит на ближайшем столбе."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "С-1",
|
|
||||||
"name": "Столовая",
|
|
||||||
"text": "В столовой пахнет хлоркой, висит плакат чистоты, там вы никого не нашли."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "С-2",
|
|
||||||
"name": "Стадион",
|
|
||||||
"text": "На стадионе вы встречаете детей 3 и 4 отрядов, вы интересуетесь почему они не собирают вещи, вам рассказывают что их вожатые самые классные на земле, и они приучили их к спорту, каждое утро они даже бегали с Катей вокруг стадиона и водонапорной вышки. Но сегодня последний день и Катя почему то отправила их играть в волейбол, а бегать запретила."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "Т",
|
|
||||||
"name": "Туалет",
|
|
||||||
"text": "За туалетом вы находите пачку сигарет, и записку как у лехи, размер и бумага совпадают. На ней написано: сегодня вам сильно повезет, не сдавайся и все получится. Похоже кто-то раздавал печенье с предсказанием."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "МК",
|
|
||||||
"name": "Макс Крутов",
|
|
||||||
"text": "Перед вами парень в рваных джинсах и черной футболке, похожий на музыканта. Говорит что был в душе вчера во время дискотеки. Тёма был на дискотеке, а наши все вчера на медляках отжигали. Ну я и решил помыться. Пока он это рассказывал мимо проходил директор. Смотрите наш броненосец пиджак скинул, а я думал это его кожа."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "АК",
|
|
||||||
"name": "Артём Ковалёв",
|
|
||||||
"text": "Я следил за детьми в клубе, даже драку девочек разнял, ребята подслушали ссору Лехи с Алиной и поддерживали разные стороны, как видите очень яро. Макс отпросился в душ, с парнями вчера спортом был занят весь день."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "ДО",
|
|
||||||
"name": "Даша Орлова",
|
|
||||||
"text": "Вчера весь вечер я сидела с детьми которые не пошли на дискотеку. Но те ребята которые могли это подтвердить уже уехали домой."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "КЛ",
|
|
||||||
"name": "Кирилл Лебедев",
|
|
||||||
"text": "Мы вчера с Аней Катей и Темой дежурили на дискотеке. Потом сразу пошли на костер, это могла быть самая лучшая смена. Я рассказывал много историй вчера на костре и про историю лагеря и про новости лагеря, вспоминали смену. На улицу не выходили, а там холодно и ничего не видно позно же уже было."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "АГ",
|
|
||||||
"name": "Артём Глушко",
|
|
||||||
"text": "Артём сидит и читает книгу в своем отряде, попутно помогает ребятам собирать чемоданы, он интересуется удалось ли что-то узнать, рассказывает что они с Лехой как-то застали Макса за кражей денег из кассы, и с тех пор в их отношениях была напряженность. Он предложил вам печенье и пошел дальше помогать ребятам."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "АС",
|
|
||||||
"name": "Анна Соколова",
|
|
||||||
"text": "Лёха был ответственным человеком и всегда помогал, иногда он делал больше чем от него требовалось. Он мог и веселые старты провести, когда физрук ленится, он вёл все мероприятия лагеря со сцены, встречал проверки, мне кажется он некоторые даже устраивал чтобы лагерь лучше делать, директор даже на него скидывал какие-то бумажные дела, Лёха был очень начитанный хоть и учился на математика, любила с ним поболтать."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "КС",
|
|
||||||
"name": "Катя Светлова",
|
|
||||||
"text": "На диване в центре общей комнаты отряда вы встречаете молодую девушку, лет 20-22, в яркой оранжевой футболке с принтами, шортах и белых носках. Длинные волосы, собранные в небрежный хвост или косу, минимум макияжа. Она сидит в обнимку старших мальчиков отряда, смеётся и рассказывает какую-то историю."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "АЗ",
|
|
||||||
"name": "Алина Зайцева",
|
|
||||||
"text": "Вы находите ее на турнике, девушка спортивного телосложения. Слезы бегут по ее лицу, на ваши вопросы она не отвечает. Про убийство ей сообщили первой."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "ВСГ",
|
|
||||||
"name": "Виктор Сергеевич Громов",
|
|
||||||
"text": "Труп обнаружил охранник Виктор Петрович на ночном обходе, позвал меня, после проверки пульса я позвонил в полицию. Вот вам список работников лагеря. страшно осознавать что кто-то из них может быть убийцей.",
|
|
||||||
"applications": [
|
|
||||||
{
|
|
||||||
"name": "Список работников лагеря"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "ЕО",
|
|
||||||
"name": "Елена Орлова",
|
|
||||||
"text": "Очень красивая статная девушка в белом халате встречает вас нежной улыбкой. Во время дискотеки я была в приемной, королевская ночь по статистике самая травмоопасная. Хотя на удивление только одна девочка с температурой, я выдала таблетку и вожатая увела ее обратно в отряд."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "СС",
|
|
||||||
"name": "Сергей Смирнов",
|
|
||||||
"text": "В небольшой коморке, залитой ярким солнцем, вы подошли к мужчине среднего возраста. «Спал я наверно, может телевизор смотрел. Что мне еще делать ночью? На дискотеки ходить? Днем дел много, не только своих, там помоги, сям помоги, никто ничего не может, вот и помогаю. Устаю, возраст как никак. Петрович вот попросил, вчера помочь с вывозом мусора, поболтали мы, да и пошел я к себе»."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "ВПБ",
|
|
||||||
"name": "Виктор Петрович Белов",
|
|
||||||
"text": "У нас режим, завтрак в 9 утра, вынос мусора в 9 вечера, в 23:00 обход. На обходе я и обнаружил тело, отходил в пол десятого это до клуба проверить все ли спокойно, и за клубом, площади проверил, минут 30 заняло, фонари перегорели у клуба, пришлось с фонариком по кустам полазить. Как тело обнаружил перепугался и сразу в администрацию.",
|
|
||||||
"applications": [
|
|
||||||
{
|
|
||||||
"name": "Карта лагеря"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||