Compare commits

..

128 Commits

Author SHA1 Message Date
VLADIMIR af7ad13ca4 up 2026-06-02 22:05:51 +07:00
VLADIMIR 3b6d82077f update design 2026-05-29 09:34:00 +07:00
VLADIMIR 6f4c5a3aa7 update clients 2026-05-29 09:05:24 +07:00
VLADIMIR 544fac2a76 add link doors 2026-05-29 09:03:46 +07:00
VLADIMIR 813493ffa7 add format string 2026-05-29 08:48:51 +07:00
VLADIMIR 235faddaa8 fix update place 2026-05-29 08:11:45 +07:00
VLADIMIR bed126bd85 add all field to admin panel 2026-05-29 07:38:46 +07:00
VLADIMIR 26f5760987 up 2026-05-17 17:15:33 +07:00
VLADIMIR 0c987d39b3 ups 2026-05-17 16:09:33 +07:00
VLADIMIR ae63de364f ups script 2026-05-17 14:30:48 +07:00
VLADIMIR 2f5dd75460 fix 2026-05-17 11:56:46 +07:00
VLADIMIR 7f88fc6b0f fix 2026-05-16 15:23:17 +07:00
VLADIMIR b8b6d86393 fix 2026-05-16 14:43:10 +07:00
VLADIMIR ae20c57ecb update building 2026-05-16 11:43:01 +07:00
VLADIMIR f23a0c2152 update routes 2026-05-16 11:10:36 +07:00
VLADIMIR f1c69c0f1f up go 2026-05-16 00:50:26 +07:00
VLADIMIR 8c2dd57e08 updates 2026-05-11 11:45:08 +07:00
VLADIMIR a107faa366 fix 2026-05-10 22:23:43 +07:00
VLADIMIR b173f6d5c4 up 2026-04-21 00:06:21 +07:00
VLADIMIR f76a23ffc9 up client 2026-04-20 23:43:58 +07:00
VLADIMIR 40c36bb784 up 2026-03-28 04:00:02 +07:00
VLADIMIR 1a51f10c54 update 2026-03-28 03:10:36 +07:00
VLADIMIR 96346bd63b fix 2026-03-26 11:57:48 +07:00
VLADIMIR 129f0fdfbd add ico 2026-03-25 14:08:39 +07:00
VLADIMIR 6515c45a1c fix 2026-03-25 13:16:30 +07:00
VLADIMIR a1b83ff514 fix 2026-03-25 13:10:35 +07:00
VLADIMIR 27ac629910 fix 2026-03-25 13:10:27 +07:00
VLADIMIR f1784fad7e update icons 2026-03-25 12:50:32 +07:00
VLADIMIR 8864043012 new design 2026-03-25 00:02:56 +07:00
VLADIMIR b54e9e714e update 2026-03-24 02:42:34 +07:00
VLADIMIR 2470fc071b fix 2026-03-24 02:20:13 +07:00
VLADIMIR 3fd528fbbd up 2026-03-22 04:33:48 +07:00
VLADIMIR aba3fb0eb1 updates 2026-03-20 03:36:02 +07:00
VLADIMIR 825d330056 add application number 2026-03-16 01:56:50 +07:00
VLADIMIR 3a30123096 add images 2026-03-14 17:01:11 +07:00
VLADIMIR ae82f8e0d9 add images 2026-03-14 16:02:33 +07:00
VLADIMIR 4ae2715ab0 update client 2026-03-07 22:39:31 +07:00
VLADIMIR 05cab1df89 fix 2026-03-07 21:25:32 +07:00
VLADIMIR 2e8afd3948 fix 2026-03-07 20:53:59 +07:00
VLADIMIR edd3db35ae add dialogs 2026-03-07 20:13:07 +07:00
VLADIMIR 856f12f2e3 clear 2026-03-07 19:23:00 +07:00
VLADIMIR 6ad47cbc38 add doors 2026-03-07 19:15:24 +07:00
VLADIMIR 83868cc778 fix 2026-03-07 07:50:24 +07:00
VLADIMIR 795edad998 add tests 2026-03-07 06:07:15 +07:00
VLADIMIR 3612805009 clear 2026-03-07 05:30:18 +07:00
VLADIMIR 186d09ba5a add mocks 2026-03-07 04:38:44 +07:00
VLADIMIR 645f6a7246 add story service 2026-03-07 04:34:58 +07:00
VLADIMIR 9e0a19d25a clear 2026-03-07 03:57:45 +07:00
VLADIMIR c3e5654ab4 add format test 2026-03-02 03:15:36 +07:00
VLADIMIR 1c63cc1747 add mock 2026-03-02 03:06:54 +07:00
VLADIMIR ccc4f126f6 add cleaner tests 2026-03-02 02:58:26 +07:00
VLADIMIR 2bc2bf45c7 add db module 2026-03-02 02:50:43 +07:00
VLADIMIR 5ab7ae0fcd clear 2026-03-02 02:25:22 +07:00
VLADIMIR caaed14ebc clear pdf 2026-03-02 02:01:55 +07:00
VLADIMIR 6cbf29031c fix 2026-03-02 01:52:33 +07:00
VLADIMIR a044093747 clear 2026-03-02 01:52:10 +07:00
VLADIMIR 4280d5376a fix 2026-03-02 01:46:33 +07:00
VLADIMIR 3b9c77b422 clear 2026-03-02 01:43:46 +07:00
VLADIMIR 5604732fcb clear 2026-03-02 01:27:50 +07:00
VLADIMIR 1964f4241e fix 2026-03-02 00:33:28 +07:00
VLADIMIR cf47f1979f rm tests 2026-03-02 00:31:41 +07:00
VLADIMIR 18acd58ff3 update 2026-03-01 02:01:15 +07:00
VLADIMIR 80b7877a09 add collapse items 2026-03-01 01:28:38 +07:00
VLADIMIR 1d157f284d fix 2026-02-28 23:57:21 +07:00
VLADIMIR ead2657a22 update editor 2025-12-14 17:00:53 +07:00
VLADIMIR a37b70b92a add styles 2025-12-14 14:28:26 +07:00
VLADIMIR 6b18709e61 add crud for node 2025-12-13 21:31:45 +07:00
VLADIMIR 3b182d7380 update edit node 2025-12-13 20:35:18 +07:00
VLADIMIR 48e7adace0 fix text 2025-12-07 23:09:51 +07:00
VLADIMIR 468256b908 update editor funcs 2025-12-07 22:50:42 +07:00
VLADIMIR ad248e3041 fix 2025-12-07 18:37:04 +07:00
VLADIMIR 8e45531b8d update 2025-12-07 05:32:17 +07:00
VLADIMIR 383e12b718 update editor 2025-12-07 04:59:43 +07:00
VLADIMIR 49c415d4b9 add update node 2025-12-07 03:41:45 +07:00
VLADIMIR 2192bf4e77 add load and save methods 2025-12-07 02:13:48 +07:00
VLADIMIR ee76743097 clear 2025-12-07 02:06:32 +07:00
VLADIMIR 09b04de9c3 update editor 2025-12-07 01:34:42 +07:00
VLADIMIR d372104760 update editor 2025-12-06 18:45:52 +07:00
VLADIMIR e1a6be0836 fix graph 2025-12-06 16:59:13 +07:00
VLADIMIR 0fe8b77d12 add stat and fix get place 2025-12-06 16:50:06 +07:00
VLADIMIR dad8d1c3a2 fix 2025-09-23 03:28:19 +07:00
VLADIMIR 93e91ea6c0 add graph 2025-09-23 03:09:57 +07:00
VLADIMIR c144123cff add graph 2025-09-23 03:04:20 +07:00
VLADIMIR 9b7241031c update ui 2025-09-23 01:11:02 +07:00
VLADIMIR 0657f36206 update 2025-09-23 01:07:24 +07:00
VLADIMIR 9f484366bb update readme 2025-07-15 02:27:24 +07:00
VLADIMIR 7585e84fad update qr 2025-07-15 02:05:18 +07:00
VLADIMIR 6707967fb5 add embed ui 2025-07-15 01:41:18 +07:00
VLADIMIR 88de56c140 rm editor 2025-07-13 04:46:02 +07:00
VLADIMIR d56f7ec990 up 2025-07-13 03:33:40 +07:00
VLADIMIR fc944329a5 up 2025-07-13 03:23:39 +07:00
VLADIMIR 52108d17bb fix 2025-06-17 03:17:45 +07:00
VLADIMIR 9cc6646fe3 add pdf 2025-06-17 03:10:31 +07:00
VLADIMIR be8e7fa482 add pdf 2025-06-17 01:40:55 +07:00
VLADIMIR b17599eb39 add time 2025-06-15 01:02:46 +07:00
VLADIMIR 36aaa49273 fix double applications 2025-06-15 00:28:48 +07:00
VLADIMIR b6e3fb8596 add teams tests 2025-06-14 23:18:05 +07:00
VLADIMIR 54706f0aba add db filepath 2025-06-14 21:40:38 +07:00
VLADIMIR 3a2980e9d0 add test story 2025-06-14 21:28:46 +07:00
VLADIMIR 77854681a0 add launch.json 2025-06-13 21:51:29 +07:00
VLADIMIR b852bebd63 add tests 2025-06-13 13:01:18 +07:00
VLADIMIR 2fa0be8bcb fix 2025-06-07 22:24:14 +07:00
VLADIMIR c5c9f22f69 fix 2025-06-07 22:23:07 +07:00
VLADIMIR ba0f0caecf fix 2025-06-07 22:21:42 +07:00
VLADIMIR 2e510b22a4 update folders 2025-06-07 22:20:14 +07:00
VLADIMIR e7668754fd update 2025-06-06 18:33:52 +07:00
VLADIMIR 94833338f9 update 2025-06-05 00:22:32 +07:00
VLADIMIR 3c1514a144 add qr 2025-06-04 02:38:01 +07:00
VLADIMIR 6b60c4e533 up 2025-06-03 03:08:23 +07:00
VLADIMIR 320273738d update 2025-06-03 00:40:18 +07:00
VLADIMIR 06f1128ad0 fix 2025-06-01 17:24:40 +07:00
VLADIMIR 42dc516e29 add game 2025-05-31 04:17:55 +07:00
VLADIMIR 49999e9e70 update and fix 2025-05-29 02:37:10 +07:00
VLADIMIR 63d1e85c7f up front 2025-05-20 03:40:34 +07:00
VLADIMIR d380fa1f46 ud front 2025-05-20 03:29:59 +07:00
VLADIMIR 153b50d3a2 add url 2025-05-20 02:46:03 +07:00
VLADIMIR e38b7ebb88 up front 2025-05-20 02:06:10 +07:00
VLADIMIR 7de3a6324f fix login 2025-05-20 02:04:13 +07:00
VLADIMIR e0d39bbd72 rm db 2025-05-18 21:53:20 +00:00
VLADIMIR 9b921fe1fa up 2025-05-19 04:52:44 +07:00
VLADIMIR e7dfcd8b4d clear 2025-05-19 04:03:43 +07:00
VLADIMIR 4b44bad1c0 up 2025-05-19 03:56:20 +07:00
VLADIMIR e409a69a54 updates 2025-05-19 03:18:22 +07:00
VLADIMIR 107504317e add place name 2025-05-18 19:26:16 +07:00
VLADIMIR 73838d7773 add logic 2025-05-17 12:30:24 +07:00
VLADIMIR e1b342d12c add actions and auth 2025-05-17 12:08:44 +07:00
VLADIMIR 4bd18ee756 add db 2025-05-17 04:36:06 +07:00
VLADIMIR 8643af86ee add service 2025-05-17 03:22:10 +07:00
87 changed files with 5541 additions and 1462 deletions
Vendored
BIN
View File
Binary file not shown.
+7
View File
@@ -17,9 +17,16 @@
# 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
.idea .idea
go.sum go.sum
store.db
cmd/text_to_story/*.txt
cmd/text_to_story/*.json
data/
+36
View File
@@ -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"
}
}
]
}
+54 -1
View File
@@ -1,5 +1,58 @@
{ {
"cSpell.words": [ "cSpell.words": [
"gwmux" "ввода",
"внимательно",
"вопрос",
"второй",
"дает",
"Дело",
"Детективы",
"диалога",
"диалогом",
"другой",
"задать",
"запущен",
"корректный",
"Можно",
"Название",
"найдена",
"найдено",
"Нельзя",
"Открываем",
"Открытие",
"открытую",
"открыть",
"получение",
"после",
"правила",
"Приложение",
"приложением",
"проходами",
"проходом",
"сервер",
"скрытой",
"скрытую",
"существует",
"сходить",
"Такой",
"Текст",
"Точка",
"точки",
"точку",
"читайте",
"AUTOINCREMENT",
"GOARCH",
"gopdf",
"gwmux",
"localtime",
"palces",
"protoc",
"qrcode",
"signintech",
"stretchr"
],
"makefile.configureOnOpen": false,
"go.testFlags": [
"-count=1",
] ]
} }
+33
View File
@@ -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 -1
View File
@@ -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"]
+2
View File
@@ -0,0 +1,2 @@
FROM golang:1.26-alpine
RUN apk add --no-cache gcc musl-dev
+31 -2
View File
@@ -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
+8 -2
View File
@@ -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
```
+118 -29
View File
@@ -34,19 +34,20 @@ service EveningDetective {
rpc GetTeam(GetTeamReq) returns (GetTeamRsp) { rpc GetTeam(GetTeamReq) returns (GetTeamRsp) {
option (google.api.http) = { option (google.api.http) = {
get: "/teams/{id}" get: "/team"
};
}
rpc DeleteTeams(DeleteTeamsReq) returns (DeleteTeamsRsp) {
option (google.api.http) = {
delete: "/teams"
}; };
} }
rpc AddAction(AddActionReq) returns (AddActionRsp) { rpc AddAction(AddActionReq) returns (AddActionRsp) {
option (google.api.http) = { option (google.api.http) = {
post: "/actions" post: "/team/actions",
body: "*"
};
}
rpc GetGame(GetGameReq) returns (GetGameRsp) {
option (google.api.http) = {
get: "/game"
}; };
} }
@@ -70,6 +71,25 @@ service EveningDetective {
body: "*" 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 {}
@@ -89,8 +109,8 @@ message AddTeamsRsp {
} }
message TeamFull { message TeamFull {
int64 id = 1; int64 id = 1;
string name = 2; string name = 2;
string password = 3; string password = 3;
} }
@@ -107,37 +127,57 @@ message GetTeamsCSVRsp {
} }
message TeamAdvanced { message TeamAdvanced {
int64 id = 1; int64 id = 1;
string name = 2; string name = 2;
int64 spendTime = 3; string password = 3;
repeated Application applications = 4; string url = 4;
int64 spendTime = 5;
repeated Application applications = 6;
} }
message Application { message Application {
string name = 1; int64 id = 1;
string name = 2;
string state = 3;
string number = 4;
} }
message GetTeamReq { message Door {
int64 id = 1; string code = 1;
string name = 2;
bool show = 3;
} }
message GetTeamReq {}
message GetTeamRsp { message GetTeamRsp {
repeated AddActionRsp actions = 1; string name = 1;
repeated Action actions = 2;
} }
message DeleteTeamsReq {}
message DeleteTeamsRsp {}
message AddActionReq { message AddActionReq {
string to = 1; string place = 1;
} }
message AddActionRsp { message AddActionRsp {}
int64 id = 1;
string to = 2; message Action {
string text = 3; int64 id = 1;
repeated Application applications = 4; string place = 2;
string name = 3;
string text = 4;
string image = 5;
repeated Application applications = 6;
bool hidden = 7;
repeated Door doors = 8;
}
message GetGameReq {}
message GetGameRsp {
string state = 1;
string startAt = 2;
string endAt = 3;
} }
message GameStartReq {} message GameStartReq {}
@@ -151,8 +191,57 @@ message GameStopReq {
message GameStopRsp {} message GameStopRsp {}
message GiveApplicationsReq { message GiveApplicationsReq {
int64 teamId = 1; int64 teamId = 1;
repeated Application applications = 2; repeated Application applications = 2;
} }
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;
}
+63
View File
@@ -0,0 +1,63 @@
### Получение игры
GET http://localhost:8090/game
### Старт игры
POST http://localhost:8090/game/start
### Стоп игры
POST http://localhost:8090/game/stop
### Получение списка комад
GET http://localhost:8090/teams
### Добавление команд
POST http://localhost:8090/teams
{
"teams": [
{
"name": "Облако"
},
{
"name": "Кустик"
}
]
}
### Получение команды
GET http://localhost:8090/team
X-Id: name
X-Password: pass
### Ход команды
POST http://localhost:8090/team/actions
X-Id: 1
X-Password: pass
{
"place": "A-1"
}
### Выдача приложения
POST http://localhost:8090/teams/1/applications
{
"applications": [
{
"id": 10
}
]
}
### Qr коды команд в pdf
GET http://localhost:8090/teams/pdf
+139 -22
View File
@@ -2,34 +2,100 @@ 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/db"
"evening_detective/internal/services/link"
"evening_detective/internal/services/pdf"
"evening_detective/internal/services/story_service"
"evening_detective/internal/services/story_storage"
proto "evening_detective/proto" 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"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata"
) )
//go:embed static/user/*
var userFS embed.FS
//go:embed static/admin/*
var adminFS embed.FS
func main() { 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
proto.RegisterEveningDetectiveServer(s, app.NewServer()) dbFilepath := config.GetDBFilepath()
// Serve gRPC server
log.Println("Serving gRPC on 0.0.0.0:8080") dbService, err := db.NewDBService(dbFilepath)
if err != nil {
log.Fatalln(err)
}
cleaner := cleaner.NewCleaner()
formatter := formatter.NewFormatter()
storyFilepath := config.GetStoryFilepath()
storyStorage := story_storage.NewFileStoryStorage(storyFilepath)
storyService, err := story_service.NewStoryService(
cleaner,
formatter,
storyStorage,
link.NewLinkService(fileHost),
)
if err != nil {
log.Fatalln(err)
}
linkService := link.NewLinkService(userClientHost)
passwordGenerator := password.NewPasswordGenerator()
pdfGenerator := pdf.NewPDFGenerator()
proto.RegisterEveningDetectiveServer(
s,
app.NewServer(
services.NewServices(
dbService,
storyService,
linkService,
passwordGenerator,
pdfGenerator,
),
),
)
// Server gRPC
lis, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatalln("Failed to listen:", err)
}
go func() { 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(
@@ -40,39 +106,90 @@ func main() {
log.Fatalln("Failed to dial server:", err) log.Fatalln("Failed to dial server:", err)
} }
gwmux := runtime.NewServeMux() gwmux := runtime.NewServeMux(
runtime.WithMetadata(func(ctx context.Context, request *http.Request) metadata.MD {
teamId := request.Header.Get("X-Id")
return metadata.Pairs("team-id", teamId)
}),
runtime.WithMetadata(func(ctx context.Context, request *http.Request) metadata.MD {
password := request.Header.Get("X-Password")
return metadata.Pairs("password", password)
}),
)
// Register Greeter // Register Greeter
err = proto.RegisterEveningDetectiveHandler(context.Background(), gwmux, conn) err = proto.RegisterEveningDetectiveHandler(context.Background(), gwmux, conn)
if err != nil { if err != nil {
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: 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 {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, Authorization, ResponseType, X-Id, X-Password")
if r.Method == "OPTIONS" {
return
}
h.ServeHTTP(w, r)
})
}
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Логируем запрос
log.Printf("[%s] %s %s", r.Method, r.URL.Path, time.Since(start))
next.ServeHTTP(w, r)
})
} }
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

@@ -4,9 +4,9 @@
<meta charset="UTF-8"> <meta charset="UTF-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>Vite App</title> <title>ВД Админка</title>
<script type="module" crossorigin src="/assets/index-waNsD4Su.js"></script> <script type="module" crossorigin src="/assets/index-c8po_p3Q.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DAw4mc7B.css"> <link rel="stylesheet" crossorigin href="/assets/index-CgpxTv-m.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

@@ -4,9 +4,9 @@
<meta charset="UTF-8"> <meta charset="UTF-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>Vite App</title> <title>Вечерний детектив</title>
<script type="module" crossorigin src="/assets/index-BKrme-n6.js"></script> <script type="module" crossorigin src="/assets/index-CQcj9qbi.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BuR9UoUk.css"> <link rel="stylesheet" crossorigin href="/assets/index-C2dfznw-.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
+68
View File
@@ -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)
}
}
+44 -3
View File
@@ -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
+27 -9
View File
@@ -1,17 +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 ( require (
golang.org/x/net v0.23.0 // indirect github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 // indirect
golang.org/x/sys v0.18.0 // indirect github.com/pkg/errors v0.8.1 // indirect
golang.org/x/text v0.15.0 // indirect )
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect require (
google.golang.org/protobuf v1.34.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/stretchr/testify v1.10.0
gopkg.in/yaml.v3 v3.0.1 // indirect
)
require (
github.com/mattn/go-sqlite3 v1.14.28
github.com/signintech/gopdf v0.32.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
golang.org/x/net v0.41.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.28.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
google.golang.org/protobuf v1.36.7
) )
+41 -21
View File
@@ -2,52 +2,72 @@ package app
import ( import (
"context" "context"
"evening_detective/internal/services"
proto "evening_detective/proto" proto "evening_detective/proto"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
) )
type Server struct { type Server struct {
proto.UnimplementedEveningDetectiveServer proto.UnimplementedEveningDetectiveServer
services *services.Services
} }
func NewServer() *Server { func NewServer(
return &Server{} services *services.Services,
) *Server {
return &Server{
services: services,
}
} }
func (s *Server) Ping(_ context.Context, _ *proto.PingReq) (*proto.PingRsp, error) { func (s *Server) Ping(_ context.Context, _ *proto.PingReq) (*proto.PingRsp, error) {
return &proto.PingRsp{}, nil return &proto.PingRsp{}, nil
} }
func (s *Server) AddTeams(context.Context, *proto.AddTeamsReq) (*proto.AddTeamsRsp, error) { func (s *Server) AddTeams(ctx context.Context, req *proto.AddTeamsReq) (*proto.AddTeamsRsp, error) {
return nil, status.Errorf(codes.Unimplemented, "method AddTeams not implemented") return s.services.AddTeams(ctx, req)
} }
func (s *Server) GetTeams(context.Context, *proto.GetTeamsReq) (*proto.GetTeamsRsp, error) { func (s *Server) GetTeams(ctx context.Context, req *proto.GetTeamsReq) (*proto.GetTeamsRsp, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetTeams not implemented") return s.services.GetTeams(ctx, req)
} }
func (s *Server) GetTeamsCSV(context.Context, *proto.GetTeamsCSVReq) (*proto.GetTeamsCSVRsp, error) { func (s *Server) GetTeamsCSV(ctx context.Context, req *proto.GetTeamsCSVReq) (*proto.GetTeamsCSVRsp, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetTeamsCSV not implemented") return s.services.GetTeamsCSV(ctx, req)
} }
func (s *Server) DeleteTeams(context.Context, *proto.DeleteTeamsReq) (*proto.DeleteTeamsRsp, error) { func (s *Server) GetTeam(ctx context.Context, req *proto.GetTeamReq) (*proto.GetTeamRsp, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteTeams not implemented") return s.services.GetTeam(ctx, req)
} }
func (s *Server) AddAction(context.Context, *proto.AddActionReq) (*proto.AddActionRsp, error) { func (s *Server) AddAction(ctx context.Context, req *proto.AddActionReq) (*proto.AddActionRsp, error) {
return nil, status.Errorf(codes.Unimplemented, "method AddAction not implemented") return s.services.AddAction(ctx, req)
} }
func (s *Server) GameStart(context.Context, *proto.GameStartReq) (*proto.GameStartRsp, error) { func (s *Server) GetGame(ctx context.Context, req *proto.GetGameReq) (*proto.GetGameRsp, error) {
return nil, status.Errorf(codes.Unimplemented, "method GameStart not implemented") return s.services.GetGame(ctx, req)
} }
func (s *Server) GameStop(context.Context, *proto.GameStopReq) (*proto.GameStopRsp, error) { func (s *Server) GameStart(ctx context.Context, req *proto.GameStartReq) (*proto.GameStartRsp, error) {
return nil, status.Errorf(codes.Unimplemented, "method GameStop not implemented") return s.services.GameStart(ctx, req)
} }
func (s *Server) GiveApplications(context.Context, *proto.GiveApplicationsReq) (*proto.GiveApplicationsRsp, error) { func (s *Server) GameStop(ctx context.Context, req *proto.GameStopReq) (*proto.GameStopRsp, error) {
return nil, status.Errorf(codes.Unimplemented, "method GiveApplications not implemented") return s.services.GameStop(ctx, req)
}
func (s *Server) GiveApplications(ctx context.Context, req *proto.GiveApplicationsReq) (*proto.GiveApplicationsRsp, error) {
return s.services.GiveApplications(ctx, req)
}
func (s *Server) DownloadTeamsQrCodesFile(ctx context.Context, req *proto.DownloadTeamsQrCodesFileReq) (*proto.DownloadTeamsQrCodesFileRsp, error) {
return s.services.DownloadTeamsQrCodesFile(ctx, req)
}
func (s *Server) GetGraph(ctx context.Context, req *proto.GetGraphReq) (*proto.GetGraphRsp, error) {
return s.services.GetGraph(ctx, req)
}
func (s *Server) UpdateNode(ctx context.Context, req *proto.UpdateNodeReq) (*proto.UpdateNodeRsp, error) {
return s.services.UpdateNode(ctx, req)
} }
+118
View File
@@ -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
}
+8
View File
@@ -0,0 +1,8 @@
package models
type Action struct {
ID int64
Place string
TeamID int64
Applications []*Application
}
+8
View File
@@ -0,0 +1,8 @@
package models
type Application struct {
ID int64
TeamID int64
Name string
State string
}
+7
View File
@@ -0,0 +1,7 @@
package models
type Game struct {
State string
StartTime string
EndTime string
}
+8
View File
@@ -0,0 +1,8 @@
package models
type Team struct {
ID int64
Name string
Password string
Link string
}
+6
View File
@@ -0,0 +1,6 @@
package cleaner
type ICleaner interface {
ClearCode(code string) string
ClearText(text string) string
}
+49
View File
@@ -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, ""))
}
+72
View File
@@ -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)
}
})
}
}
+6
View File
@@ -0,0 +1,6 @@
package formatter
type IFormatter interface {
FormatText(text string) string
FormatString(text string) string
}
+50
View File
@@ -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)
}
})
}
}
+5
View File
@@ -0,0 +1,5 @@
package password
type IPasswordGenerator interface {
GeneratePassword(length int) string
}
+21
View File
@@ -0,0 +1,21 @@
package password
import "math/rand"
var (
letters = []rune("abcdefghijklmnopqrstuvwxyz123456789")
)
type service struct{}
func NewPasswordGenerator() IPasswordGenerator {
return &service{}
}
func (s *service) GeneratePassword(length int) string {
b := make([]rune, length)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
+28
View File
@@ -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
}
+223
View File
@@ -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)
}
+236
View File
@@ -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
}
+6
View File
@@ -0,0 +1,6 @@
package link
type ILinkService interface {
GetTeamClientLink(name string, password string) string
GetImageLink(name string) string
}
+27
View File
@@ -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)
}
+100
View File
@@ -0,0 +1,100 @@
package services
import (
"evening_detective/internal/models"
story_service_models "evening_detective/internal/services/story_service/models"
"evening_detective/proto"
"strings"
)
func mapTeamsToTeamAdvanced(team *models.Team) *proto.TeamAdvanced {
return &proto.TeamAdvanced{
Id: team.ID,
Name: team.Name,
Password: team.Password,
}
}
func mapTeamsToTeamFull(team *models.Team) *proto.TeamFull {
return &proto.TeamFull{
Id: team.ID,
Name: team.Name,
Password: team.Password,
}
}
func mapProtoTeamsToTeam(team *proto.Team) *models.Team {
return &models.Team{
Name: clearTeamName(team.Name),
}
}
func mapPlaceToProtoAction(place *story_service_models.Place) *proto.Action {
return &proto.Action{
Place: place.Code,
}
}
func mapStoryApplicationToProtoApplication(application *story_service_models.Application) *proto.Application {
return &proto.Application{
Name: application.Name,
Number: application.Number,
}
}
func mapStoryDoorToProtoDoor(door *story_service_models.Door) *proto.Door {
return &proto.Door{
Code: door.Code,
Name: door.Name,
Show: door.Show,
}
}
func mapStoryApplicationsToApplications(applications []*story_service_models.Application) []*models.Application {
res := make([]*models.Application, 0, len(applications))
for _, application := range applications {
res = append(res, mapStoryApplicationToApplication(application))
}
return res
}
func mapStoryApplicationToApplication(application *story_service_models.Application) *models.Application {
return &models.Application{
Name: application.Name,
State: "NEW",
}
}
func mapApplicationsToProtoApplications(applications []*models.Application) []*proto.Application {
res := make([]*proto.Application, 0, len(applications))
for _, application := range applications {
res = append(res, mapApplicationToProtoApplication(application))
}
return res
}
func mapApplicationToProtoApplication(application *models.Application) *proto.Application {
return &proto.Application{
Id: application.ID,
Name: application.Name,
State: application.State,
}
}
func mapProtoApplicationsToApplications(items []*proto.Application) []*models.Application {
res := make([]*models.Application, 0, len(items))
for _, item := range items {
res = append(res, mapProtoApplicationToApplication(item))
}
return res
}
func mapProtoApplicationToApplication(items *proto.Application) *models.Application {
return &models.Application{
ID: items.Id,
}
}
func clearTeamName(code string) string {
return strings.TrimSpace(code)
}
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
package pdf
import "evening_detective/internal/models"
type IPDFGenerator interface {
CreateTeamsPDF(teams []*models.Team) ([]byte, error)
}
+135
View File
@@ -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
}
+345
View File
@@ -0,0 +1,345 @@
package services
import (
"context"
"encoding/base64"
"encoding/json"
"evening_detective/internal/models"
"evening_detective/internal/modules/password"
"evening_detective/internal/services/db"
"evening_detective/internal/services/link"
"evening_detective/internal/services/pdf"
"evening_detective/internal/services/story_service"
"evening_detective/proto"
"fmt"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
type Services struct {
dbService db.IDBService
storyService *story_service.StoryService
linkService link.ILinkService
passwordGenerator password.IPasswordGenerator
pdfGenerator pdf.IPDFGenerator
}
func NewServices(
dbService db.IDBService,
storyService *story_service.StoryService,
linkService link.ILinkService,
passwordGenerator password.IPasswordGenerator,
pdfGenerator pdf.IPDFGenerator,
) *Services {
return &Services{
dbService: dbService,
storyService: storyService,
linkService: linkService,
passwordGenerator: passwordGenerator,
pdfGenerator: pdfGenerator,
}
}
func (s *Services) GiveApplications(ctx context.Context, req *proto.GiveApplicationsReq) (*proto.GiveApplicationsRsp, error) {
applications := mapProtoApplicationsToApplications(req.Applications)
if err := s.dbService.GiveApplications(ctx, req.TeamId, applications); err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
return &proto.GiveApplicationsRsp{}, nil
}
func (s *Services) GetGame(ctx context.Context, _ *proto.GetGameReq) (*proto.GetGameRsp, error) {
game, err := s.dbService.GetGame(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
return &proto.GetGameRsp{
State: game.State,
StartAt: game.StartTime,
EndAt: game.EndTime,
}, nil
}
func (s *Services) GameStart(ctx context.Context, _ *proto.GameStartReq) (*proto.GameStartRsp, error) {
if err := s.dbService.UpdateGameState(ctx, "RUN"); err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
return &proto.GameStartRsp{}, nil
}
func (s *Services) GameStop(ctx context.Context, req *proto.GameStopReq) (*proto.GameStopRsp, error) {
if err := s.dbService.UpdateGameState(ctx, "STOP"); err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
return &proto.GameStopRsp{}, nil
}
func (s *Services) AddAction(ctx context.Context, req *proto.AddActionReq) (*proto.AddActionRsp, error) {
team, err := s.getTeam(ctx)
if err != nil {
return nil, err
}
place := s.storyService.GetPlace(req.Place)
actions := []*models.Action{
{
Place: place.Code,
TeamID: team.ID,
Applications: mapStoryApplicationsToApplications(place.Applications),
},
}
if err := s.dbService.AddActions(ctx, team.ID, actions); err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
currentApplications, err := s.dbService.GetApplications(ctx, team.ID)
if err != nil {
return nil, err
}
newApplications := make([]*models.Application, 0, len(actions))
for _, action := range actions {
for _, actionApplication := range action.Applications {
f := false
for _, currentApplication := range currentApplications {
if currentApplication.Name == actionApplication.Name {
f = true
}
}
if !f {
newApplications = append(newApplications, actionApplication)
}
}
}
if err := s.dbService.AddApplications(ctx, team.ID, newApplications); err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
addLog(team, "add action", actions)
return &proto.AddActionRsp{}, nil
}
func (s *Services) GetTeam(ctx context.Context, req *proto.GetTeamReq) (*proto.GetTeamRsp, error) {
team, err := s.getTeam(ctx)
if err != nil {
return nil, err
}
actions, err := s.dbService.GetActions(ctx, team.ID)
if err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
actionsCodes := make([]string, 0, len(actions))
for _, action := range actions {
actionsCodes = append(actionsCodes, action.Place)
}
res := make([]*proto.Action, 0, len(actions))
for i, place := range s.storyService.GetPlaces(actionsCodes) {
newAction := mapPlaceToProtoAction(place)
newAction.Id = actions[i].ID
newAction.Name = place.Name
newAction.Text = place.Text
newAction.Image = place.Image
newAction.Applications = make([]*proto.Application, 0, len(place.Applications))
for _, application := range place.Applications {
newAction.Applications = append(newAction.Applications, mapStoryApplicationToProtoApplication(application))
}
newAction.Hidden = place.Hidden
newAction.Doors = make([]*proto.Door, 0, len(place.Doors))
for _, door := range place.Doors {
newAction.Doors = append(newAction.Doors, mapStoryDoorToProtoDoor(door))
}
res = append(res, newAction)
}
return &proto.GetTeamRsp{
Name: team.Name,
Actions: res,
}, err
}
func (s *Services) GetTeamsCSV(ctx context.Context, req *proto.GetTeamsCSVReq) (*proto.GetTeamsCSVRsp, error) {
panic("unimplemented")
}
func (s *Services) GetTeams(ctx context.Context, _ *proto.GetTeamsReq) (*proto.GetTeamsRsp, error) {
teams, err := s.dbService.GetTeams(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
res := make([]*proto.TeamAdvanced, 0, len(teams))
for _, team := range teams {
newTeam := mapTeamsToTeamAdvanced(team)
actions, err := s.dbService.GetActions(ctx, team.ID)
if err != nil {
return nil, err
}
newTeam.Url = s.linkService.GetTeamClientLink(team.Name, team.Password)
newTeam.SpendTime = int64(len(actions))
currentApplications, err := s.dbService.GetApplicationsByState(ctx, team.ID, "NEW")
if err != nil {
return nil, err
}
newTeam.Applications = mapApplicationsToProtoApplications(currentApplications)
res = append(res, newTeam)
}
return &proto.GetTeamsRsp{Teams: res}, err
}
func (s *Services) AddTeams(ctx context.Context, req *proto.AddTeamsReq) (*proto.AddTeamsRsp, error) {
inTeams := make([]*models.Team, 0, len(req.Teams))
for _, team := range req.Teams {
t := mapProtoTeamsToTeam(team)
t.Password = s.passwordGenerator.GeneratePassword(8)
inTeams = append(inTeams, t)
}
teams, err := s.dbService.AddTeams(ctx, inTeams)
if err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
res := make([]*proto.TeamFull, 0, len(teams))
for _, team := range teams {
res = append(res, mapTeamsToTeamFull(team))
}
return &proto.AddTeamsRsp{Teams: res}, nil
}
func (s *Services) DownloadTeamsQrCodesFile(ctx context.Context, req *proto.DownloadTeamsQrCodesFileReq) (*proto.DownloadTeamsQrCodesFileRsp, error) {
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
teams, err := s.dbService.GetTeams(ctx)
if err != nil {
return nil, err
}
for _, team := range teams {
team.Link = s.linkService.GetTeamClientLink(team.Name, team.Password)
}
b, err := s.pdfGenerator.CreateTeamsPDF(teams)
if err != nil {
return nil, err
}
return &proto.DownloadTeamsQrCodesFileRsp{Result: b}, nil
}
func (s *Services) UpdateNode(ctx context.Context, req *proto.UpdateNodeReq) (*proto.UpdateNodeRsp, error) {
applications := make([]*story_service.GraphApplication, 0, len(req.Node.Applications))
for _, application := range req.Node.Applications {
applications = append(
applications,
&story_service.GraphApplication{
Name: application.Name,
},
)
}
doors := make([]*story_service.GraphDoor, 0, len(req.Node.Doors))
for _, door := range req.Node.Doors {
doors = append(
doors,
&story_service.GraphDoor{
Code: door.Code,
Name: door.Name,
Show: door.Show,
},
)
}
node := &story_service.GraphNode{
Code: req.Node.Code,
Name: req.Node.Name,
Text: req.Node.Text,
Image: req.Node.Image,
Applications: applications,
Hidden: req.Node.Hidden,
Doors: doors,
}
if err := s.storyService.UpdatePlace(ctx, req.Code, node); err != nil {
return nil, err
}
return &proto.UpdateNodeRsp{}, nil
}
func (s *Services) GetGraph(ctx context.Context, req *proto.GetGraphReq) (*proto.GetGraphRsp, error) {
graph := s.storyService.GetGraph(ctx)
nodes := make([]*proto.GraphNode, 0, len(graph.Nodes))
for _, node := range graph.Nodes {
applications := make([]*proto.GraphApplication, 0, len(node.Applications))
for _, application := range node.Applications {
applications = append(
applications,
&proto.GraphApplication{
Name: application.Name,
},
)
}
doors := make([]*proto.GraphDoor, 0, len(node.Doors))
for _, door := range node.Doors {
doors = append(
doors,
&proto.GraphDoor{
Code: door.Code,
Name: door.Name,
Show: door.Show,
},
)
}
nodes = append(nodes, &proto.GraphNode{
Code: node.Code,
Name: node.Name,
Text: node.Text,
Image: node.Image,
Applications: applications,
Hidden: node.Hidden,
Doors: doors,
})
}
edges := make([]*proto.GetGraphRsp_Edge, 0, len(graph.Edges))
for _, edge := range graph.Edges {
edges = append(edges, &proto.GetGraphRsp_Edge{
From: edge.From,
To: edge.To,
Arrows: "to",
Type: edge.Type,
})
}
return &proto.GetGraphRsp{
Nodes: nodes,
Edges: edges,
CountNodes: int32(len(nodes)),
CountEdges: int32(len(edges)),
}, nil
}
func (s *Services) getTeam(ctx context.Context) (*models.Team, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Errorf(codes.Unauthenticated, "error creds")
}
teamIdArr, ok := md["team-id"]
if !ok {
return nil, status.Errorf(codes.Unauthenticated, "error creds")
}
teamId, err := base64.StdEncoding.DecodeString(teamIdArr[0])
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "error creds")
}
passwordArr, ok := md["password"]
if !ok {
return nil, status.Errorf(codes.Unauthenticated, "error creds")
}
password := passwordArr[0]
team, err := s.dbService.GetTeam(ctx, teamId, password)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, err.Error())
}
return team, nil
}
func addLog(team *models.Team, action string, v any) {
vJson, err := json.Marshal(v)
if err == nil {
fmt.Printf("Team %s: %s %s\n", team.Name, action, string(vJson))
}
}
@@ -0,0 +1,11 @@
package story_service
import (
"context"
"evening_detective/internal/services/story_service/models"
)
type IStoryStorage interface {
Load(ctx context.Context) (*models.Story, error)
Save(ctx context.Context, story *models.Story) error
}
@@ -0,0 +1,32 @@
package story_service
type Graph struct {
Nodes []*GraphNode
Edges []*GraphEdge
}
type GraphNode struct {
Code string
Name string
Text string
Image string
Applications []*GraphApplication
Hidden bool
Doors []*GraphDoor
}
type GraphEdge struct {
From string
To string
Type string
}
type GraphApplication struct {
Name string
}
type GraphDoor struct {
Code string
Name string
Show bool
}
@@ -0,0 +1,34 @@
package models
import "fmt"
type Application struct {
Name string `json:"name"`
Number string `json:"-"`
}
func NewApplication(
name string,
opts ...ApplicationOpt,
) *Application {
application := &Application{
Name: name,
}
for _, opt := range opts {
opt(application)
}
return application
}
type ApplicationOpt func(application *Application) error
func WithApplicationNumber(number int) ApplicationOpt {
return func(application *Application) error {
application.SetNumber(number)
return nil
}
}
func (a *Application) SetNumber(number int) {
a.Number = fmt.Sprintf("%d", number)
}
@@ -0,0 +1,31 @@
package models
type Door struct {
Code string `json:"code"`
Name string `json:"name"`
Show bool `json:"show"`
}
func NewDoor(
code string,
name string,
opts ...DoorOpt,
) *Door {
door := &Door{
Code: code,
Name: name,
}
for _, opt := range opts {
opt(door)
}
return door
}
type DoorOpt func(door *Door) error
func WithDoorShow(show bool) DoorOpt {
return func(door *Door) error {
door.Show = show
return nil
}
}
@@ -0,0 +1,70 @@
package models
type Place struct {
Code string `json:"code"`
Name string `json:"name"`
Text string `json:"text"`
Image string `json:"image"`
Applications []*Application `json:"applications,omitempty"`
Hidden bool `json:"hidden"`
Doors []*Door `json:"doors"`
}
func NewPlace(
code string,
name string,
text string,
opts ...PlaceOpt,
) *Place {
p := &Place{
Code: code,
Name: name,
Text: text,
}
for _, opt := range opts {
opt(p)
}
return p
}
func NewNotFoundPlace(code string) *Place {
return NewPlace(code, "Не найдено", "Такой точки не существует.")
}
func NewClientErrorPlace(code string) *Place {
return NewPlace(code, "Не найдено", "Детективы, внимательно читайте правила.")
}
type PlaceOpt func(place *Place) error
func WithPlaceImage(image string) PlaceOpt {
return func(place *Place) error {
place.Image = image
return nil
}
}
func WithPlaceApplication(applications ...*Application) PlaceOpt {
return func(place *Place) error {
for _, application := range applications {
place.Applications = append(place.Applications, application)
}
return nil
}
}
func WithPlaceHidden(hidden bool) PlaceOpt {
return func(place *Place) error {
place.Hidden = hidden
return nil
}
}
func WithPlaceDoors(doors ...*Door) PlaceOpt {
return func(place *Place) error {
for _, door := range doors {
place.Doors = append(place.Doors, door)
}
return nil
}
}
@@ -0,0 +1,5 @@
package models
type Story struct {
Places []*Place `json:"places"`
}
+318
View File
@@ -0,0 +1,318 @@
package story_service
import (
"context"
"evening_detective/internal/modules/cleaner"
"evening_detective/internal/modules/formatter"
"evening_detective/internal/services/link"
"evening_detective/internal/services/story_service/models"
"regexp"
"strings"
)
type StoryService struct {
cleaner cleaner.ICleaner
formatter formatter.IFormatter
story *models.Story
storyStorage IStoryStorage
linkService link.ILinkService
}
func NewStoryService(
cleaner cleaner.ICleaner,
formatter formatter.IFormatter,
storyStorage IStoryStorage,
linkService link.ILinkService,
) (*StoryService, error) {
s := &StoryService{
cleaner: cleaner,
formatter: formatter,
storyStorage: storyStorage,
linkService: linkService,
}
story, err := s.storyStorage.Load(context.Background())
if err != nil {
return nil, err
}
s.story = story
return s, nil
}
func (s *StoryService) Update(ctx context.Context) error {
if err := s.storyStorage.Save(ctx, s.story); err != nil {
return err
}
story, err := s.storyStorage.Load(ctx)
if err != nil {
return err
}
s.story = story
return nil
}
func (s *StoryService) GetPlace(code string) *models.Place {
if strings.HasPrefix(code, "[") || strings.HasSuffix(code, "]") {
return models.NewClientErrorPlace(code)
}
clearCode := s.cleaner.ClearCode(code)
for _, place := range s.story.Places {
if s.cleaner.ClearCode(place.Code) == clearCode {
applications := make([]*models.Application, 0, len(place.Applications))
for _, application := range place.Applications {
applications = append(
applications,
models.NewApplication(
s.cleaner.ClearText(application.Name),
),
)
}
doors := make([]*models.Door, 0, len(place.Doors))
for _, door := range place.Doors {
doors = append(
doors,
models.NewDoor(
door.Code,
door.Name,
models.WithDoorShow(door.Show),
),
)
}
return models.NewPlace(
place.Code,
place.Name,
s.cleaner.ClearText(place.Text),
models.WithPlaceImage(s.linkService.GetImageLink(place.Image)),
models.WithPlaceApplication(applications...),
models.WithPlaceHidden(place.Hidden),
models.WithPlaceDoors(doors...),
)
}
}
return models.NewNotFoundPlace(code)
}
func (s *StoryService) GetPlaces(codes []string) []*models.Place {
places := make([]*models.Place, 0, 100)
mOpen := map[string]any{}
mDeleted := map[string]any{}
applicationNumber := 1
applicationsMap := make(map[string]interface{}, 10)
for i, code := range codes {
place := s.GetPlace(code)
for i, application := range place.Applications {
if _, ok := applicationsMap[application.Name]; ok {
place.Applications = append(place.Applications[:i], place.Applications[i+1:]...)
if len(place.Applications) == 0 {
place.Applications = nil
}
}
applicationsMap[application.Name] = struct{}{}
application.SetNumber(applicationNumber)
applicationNumber++
}
if _, ok := mOpen[place.Code]; place.Hidden && !ok {
place = models.NewNotFoundPlace(place.Code)
}
places = append(places, place)
for j, door := range places[i].Doors {
if _, ok := mDeleted[door.Code]; ok {
places[i].Doors[j].Show = false
continue
}
mOpen[door.Code] = struct{}{}
}
if i > 0 {
for j, door := range places[i-1].Doors {
if door.Code != place.Code && door.Show {
places[i-1].Doors[j].Show = false
delete(mOpen, door.Code)
mDeleted[door.Code] = struct{}{}
}
}
}
}
return places
}
func (s *StoryService) UpdatePlace(ctx context.Context, code string, node *GraphNode) error {
if code != "" && node.Code == "" {
return s.deletePlace(ctx, code)
}
if code == "" && node.Code != "" {
return s.addPlace(ctx, node)
}
if code == "" || node.Code == "" {
return nil
}
return s.updatePlace(ctx, code, node)
}
func (s *StoryService) deletePlace(ctx context.Context, code string) error {
for i := range s.story.Places {
if s.story.Places[i].Code == code {
s.story.Places = append(s.story.Places[:i], s.story.Places[i+1:]...)
break
}
}
return s.Update(ctx)
}
func (s *StoryService) addPlace(ctx context.Context, node *GraphNode) error {
s.story.Places = append(
s.story.Places,
models.NewPlace(
node.Code,
node.Name,
s.formatter.FormatText(node.Text),
models.WithPlaceApplication(s.getApplications(node)...),
),
)
return s.Update(ctx)
}
func (s *StoryService) updatePlace(ctx context.Context, code string, node *GraphNode) error {
for i := range s.story.Places {
if s.story.Places[i].Code == code {
s.story.Places[i] = models.NewPlace(
node.Code,
s.formatter.FormatString(node.Name),
s.formatter.FormatText(node.Text),
models.WithPlaceImage(node.Image),
models.WithPlaceApplication(s.getApplications(node)...),
models.WithPlaceHidden(node.Hidden),
models.WithPlaceDoors(s.getDoors(node)...),
)
return s.Update(ctx)
}
}
for i := range s.story.Places {
if s.story.Places[i].Code == node.Code {
s.story.Places[i] = models.NewPlace(
code,
s.formatter.FormatString(node.Name),
s.formatter.FormatText(node.Text),
models.WithPlaceImage(node.Image),
models.WithPlaceApplication(s.getApplications(node)...),
models.WithPlaceHidden(node.Hidden),
models.WithPlaceDoors(s.getDoors(node)...),
)
break
}
}
return s.Update(ctx)
}
func (s *StoryService) getApplications(node *GraphNode) []*models.Application {
nodeApplications := make([]*models.Application, 0, len(node.Applications))
for _, application := range node.Applications {
nodeApplications = append(
nodeApplications,
&models.Application{
Name: s.formatter.FormatString(application.Name),
},
)
}
return nodeApplications
}
func (s *StoryService) getDoors(node *GraphNode) []*models.Door {
nodeDoors := make([]*models.Door, 0, len(node.Doors))
for _, door := range node.Doors {
nodeDoors = append(
nodeDoors,
&models.Door{
Code: door.Code,
Name: s.formatter.FormatString(door.Name),
Show: door.Show,
},
)
}
return nodeDoors
}
func (s *StoryService) GetGraph(ctx context.Context) *Graph {
m := make(map[string]string, len(s.story.Places))
nodes := make([]*GraphNode, 0, len(s.story.Places))
for _, place := range s.story.Places {
m[s.cleaner.ClearCode(place.Code)] = place.Code
applications := make([]*GraphApplication, 0, len(place.Applications))
for _, application := range place.Applications {
applications = append(
applications,
&GraphApplication{
Name: application.Name,
},
)
}
doors := make([]*GraphDoor, 0, len(place.Doors))
for _, door := range place.Doors {
doors = append(
doors,
&GraphDoor{
Code: door.Code,
Name: door.Name,
Show: door.Show,
},
)
}
nodes = append(
nodes, &GraphNode{
Code: place.Code,
Name: place.Name,
Text: place.Text,
Image: place.Image,
Applications: applications,
Hidden: place.Hidden,
Doors: doors,
},
)
}
edges := make([]*GraphEdge, 0, len(s.story.Places)*3)
for _, place := range s.story.Places {
placeLinks := s.findPlaceLinksInText(place.Text)
for _, placeLink := range placeLinks {
edges = append(
edges,
&GraphEdge{
From: m[s.cleaner.ClearCode(place.Code)],
To: m[s.cleaner.ClearCode(placeLink)],
Type: "node",
},
)
}
for _, application := range place.Applications {
placeLinks := s.findPlaceLinksInText(application.Name)
for _, placeLink := range placeLinks {
edges = append(
edges,
&GraphEdge{
From: m[s.cleaner.ClearCode(place.Code)],
To: m[s.cleaner.ClearCode(placeLink)],
Type: "application",
},
)
}
}
for _, door := range place.Doors {
edges = append(
edges,
&GraphEdge{
From: m[s.cleaner.ClearCode(place.Code)],
To: m[s.cleaner.ClearCode(door.Code)],
Type: "door",
},
)
}
}
return &Graph{
Nodes: nodes,
Edges: edges,
}
}
func (s *StoryService) findPlaceLinksInText(text string) []string {
re := regexp.MustCompile(`\(\[[a-zA-Zа-яА-Я\d-]+\]\)`)
return re.FindAllString(text, -1)
}
@@ -0,0 +1,508 @@
package story_service_test
import (
"evening_detective/internal/modules/cleaner"
"evening_detective/internal/modules/formatter"
"evening_detective/internal/services/link"
"evening_detective/internal/services/story_service"
"evening_detective/internal/services/story_service/models"
"evening_detective/internal/services/story_storage"
"testing"
"github.com/stretchr/testify/assert"
)
func TestStoryService_GetPlace(t *testing.T) {
tests := []struct {
name string
story *models.Story
code string
want *models.Place
}{
{
name: "не корректный ввода",
story: &models.Story{},
code: "[Ы]",
want: models.NewClientErrorPlace("[Ы]"),
},
{
name: "точка не найдена",
story: &models.Story{},
code: "Ы",
want: models.NewNotFoundPlace("Ы"),
},
{
name: "получение точки",
story: &models.Story{
Places: []*models.Place{
models.NewPlace("Ы", "Название", "Текст"),
},
},
code: "Ы",
want: models.NewPlace("Ы", "Название", "Текст"),
},
{
name: "получение скрытой точки",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
},
},
code: "Ы",
want: models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
},
{
name: "получение точки с приложением",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceApplication(
models.NewApplication("Приложение"),
),
),
},
},
code: "Ы",
want: models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceApplication(
models.NewApplication("Приложение"),
),
),
},
{
name: "получение точки с проходом",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Й", "Приложение"),
),
),
},
},
code: "Ы",
want: models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Й", "Приложение"),
),
),
},
{
name: "получение точки с диалогом",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor(
"Й",
"Приложение",
models.WithDoorShow(true),
),
),
),
},
},
code: "Ы",
want: models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor(
"Й",
"Приложение",
models.WithDoorShow(true),
),
),
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := story_service.NewStoryService(
cleaner.NewCleaner(),
formatter.NewFormatter(),
story_storage.NewVarStoryStorage(tt.story),
link.NewLinkService("http://localhost:8120"),
)
if err != nil {
t.Fatalf("could not construct receiver type: %v", err)
}
got := s.GetPlace(tt.code)
assert.Equal(t, got, tt.want)
})
}
}
func TestStoryService_GetPlaces(t *testing.T) {
tests := []struct {
name string
story *models.Story
codes []string
want []*models.Place
}{
{
name: "Можно сходить в открытую точку",
story: &models.Story{
Places: []*models.Place{
models.NewPlace("Ы", "Название", "Текст"),
},
},
codes: []string{"Ы"},
want: []*models.Place{
models.NewPlace("Ы", "Название", "Текст"),
},
},
{
name: "Нельзя открыть скрытую точку",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
},
},
codes: []string{"Ы"},
want: []*models.Place{
models.NewNotFoundPlace("Ы"),
},
},
{
name: "Открываем скрытую точку",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название"),
models.NewDoor("Ы-3", "Название"),
),
),
models.NewPlace(
"Ы-2",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewPlace(
"Ы-3",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
},
},
codes: []string{"Ы-1", "Ы-2", "Ы-3"},
want: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название"),
models.NewDoor("Ы-3", "Название"),
),
),
models.NewPlace(
"Ы-2",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewPlace(
"Ы-3",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
},
},
{
name: "Открываем скрытую точку диалога",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
),
),
models.NewPlace(
"Ы-2",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewPlace(
"Ы-3",
"Название",
"Текст",
),
},
},
codes: []string{"Ы-1", "Ы-3", "Ы-2"},
want: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название", models.WithDoorShow(false)),
),
),
models.NewPlace(
"Ы-3",
"Название",
"Текст",
),
models.NewNotFoundPlace("Ы-2"),
},
},
{
name: "Открываем скрытую точку диалога",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
models.NewDoor("Ы-3", "Название", models.WithDoorShow(true)),
),
),
models.NewPlace(
"Ы-2",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewPlace(
"Ы-3",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
},
},
codes: []string{"Ы-1", "Ы-2", "Ы-3"},
want: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
models.NewDoor("Ы-3", "Название", models.WithDoorShow(false)),
),
),
models.NewPlace(
"Ы-2",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewNotFoundPlace("Ы-3"),
},
},
{
name: "Открываем скрытую точку после диалога",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
models.NewDoor("Ы-3", "Название", models.WithDoorShow(true)),
models.NewDoor("Ы-4", "Название"),
),
),
models.NewPlace(
"Ы-2",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewPlace(
"Ы-3",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewPlace(
"Ы-4",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
},
},
codes: []string{"Ы-1", "Ы-2", "Ы-3", "Ы-4"},
want: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
models.NewDoor("Ы-3", "Название", models.WithDoorShow(false)),
models.NewDoor("Ы-4", "Название"),
),
),
models.NewPlace(
"Ы-2",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewNotFoundPlace("Ы-3"),
models.NewPlace(
"Ы-4",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
},
},
{
name: "Открытие второй раз точки не дает задать другой вопрос",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
models.NewDoor("Ы-3", "Название", models.WithDoorShow(true)),
),
),
models.NewPlace(
"Ы-2",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewPlace(
"Ы-3",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
},
},
codes: []string{"Ы-1", "Ы-2", "Ы-3", "Ы-1", "Ы-3", "Ы-2"},
want: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
models.NewDoor("Ы-3", "Название", models.WithDoorShow(false)),
),
),
models.NewPlace(
"Ы-2",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewNotFoundPlace("Ы-3"),
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название", models.WithDoorShow(false)),
models.NewDoor("Ы-3", "Название", models.WithDoorShow(false)),
),
),
models.NewNotFoundPlace("Ы-3"),
models.NewNotFoundPlace("Ы-2"),
},
},
{
name: "Улики не повторяются",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceApplication(
models.NewApplication("Название"),
),
),
},
},
codes: []string{"Ы", "Ы"},
want: []*models.Place{
models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceApplication(
models.NewApplication(
"Название",
models.WithApplicationNumber(1),
),
),
),
models.NewPlace(
"Ы",
"Название",
"Текст",
),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := story_service.NewStoryService(
cleaner.NewCleaner(),
formatter.NewFormatter(),
story_storage.NewVarStoryStorage(tt.story),
link.NewLinkService("http://localhost:8120"),
)
assert.Nil(t, err)
got := s.GetPlaces(tt.codes)
assert.Equal(t, got, tt.want)
})
}
}
@@ -0,0 +1,47 @@
package story_storage
import (
"context"
"encoding/json"
"evening_detective/internal/services/story_service"
"evening_detective/internal/services/story_service/models"
"fmt"
"log"
"os"
)
type fileService struct {
filepath string
}
func NewFileStoryStorage(filepath string) story_service.IStoryStorage {
return &fileService{
filepath: filepath,
}
}
func (s *fileService) Load(ctx context.Context) (*models.Story, error) {
data, err := os.ReadFile(s.filepath)
if err != nil {
return nil, fmt.Errorf("story file %s not found", s.filepath)
}
log.Printf("Load story from: %s", s.filepath)
story := &models.Story{}
if err := json.Unmarshal(data, story); err != nil {
return nil, err
}
log.Printf("Found %d places", len(story.Places))
return story, nil
}
func (s *fileService) Save(ctx context.Context, story *models.Story) error {
data, err := json.Marshal(story)
if err != nil {
return err
}
if err := os.WriteFile(s.filepath, data, 0644); err != nil {
return err
}
log.Printf("save story to: %s", s.filepath)
return nil
}
@@ -0,0 +1,11 @@
package story_storage
import (
"context"
"evening_detective/internal/services/story_service/models"
)
type IStoryStorage interface {
Load(ctx context.Context) (*models.Story, error)
Save(ctx context.Context, story *models.Story) error
}
@@ -0,0 +1,26 @@
package story_storage
import (
"context"
"evening_detective/internal/services/story_service"
"evening_detective/internal/services/story_service/models"
)
type varService struct {
story *models.Story
}
func NewVarStoryStorage(story *models.Story) story_service.IStoryStorage {
return &varService{
story: story,
}
}
func (s *varService) Load(ctx context.Context) (*models.Story, error) {
return s.story, nil
}
func (s *varService) Save(ctx context.Context, story *models.Story) error {
s.story = story
return nil
}
+1201 -828
View File
File diff suppressed because it is too large Load Diff
+417 -386
View File
File diff suppressed because it is too large Load Diff
+299 -31
View File
@@ -11,14 +11,14 @@
"application/json" "application/json"
], ],
"paths": { "paths": {
"/actions": { "/csv": {
"post": { "get": {
"operationId": "EveningDetective_AddAction", "operationId": "EveningDetective_GetTeamsCSV",
"responses": { "responses": {
"200": { "200": {
"description": "A successful response.", "description": "A successful response.",
"schema": { "schema": {
"$ref": "#/definitions/evening_detectiveAddActionRsp" "$ref": "#/definitions/evening_detectiveGetTeamsCSVRsp"
} }
}, },
"default": { "default": {
@@ -33,14 +33,14 @@
] ]
} }
}, },
"/csv": { "/game": {
"get": { "get": {
"operationId": "EveningDetective_GetTeamsCSV", "operationId": "EveningDetective_GetGame",
"responses": { "responses": {
"200": { "200": {
"description": "A successful response.", "description": "A successful response.",
"schema": { "schema": {
"$ref": "#/definitions/evening_detectiveGetTeamsCSVRsp" "$ref": "#/definitions/evening_detectiveGetGameRsp"
} }
}, },
"default": { "default": {
@@ -119,6 +119,60 @@
] ]
} }
}, },
"/graph": {
"get": {
"operationId": "EveningDetective_GetGraph",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/evening_detectiveGetGraphRsp"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/runtimeError"
}
}
},
"tags": [
"EveningDetective"
]
}
},
"/graph/nodes": {
"put": {
"operationId": "EveningDetective_UpdateNode",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/evening_detectiveUpdateNodeRsp"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/runtimeError"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/evening_detectiveUpdateNodeReq"
}
}
],
"tags": [
"EveningDetective"
]
}
},
"/ping": { "/ping": {
"get": { "get": {
"operationId": "EveningDetective_Ping", "operationId": "EveningDetective_Ping",
@@ -141,14 +195,14 @@
] ]
} }
}, },
"/teams": { "/team": {
"get": { "get": {
"operationId": "EveningDetective_GetTeams", "operationId": "EveningDetective_GetTeam",
"responses": { "responses": {
"200": { "200": {
"description": "A successful response.", "description": "A successful response.",
"schema": { "schema": {
"$ref": "#/definitions/evening_detectiveGetTeamsRsp" "$ref": "#/definitions/evening_detectiveGetTeamRsp"
} }
}, },
"default": { "default": {
@@ -161,14 +215,48 @@
"tags": [ "tags": [
"EveningDetective" "EveningDetective"
] ]
}, }
"delete": { },
"operationId": "EveningDetective_DeleteTeams", "/team/actions": {
"post": {
"operationId": "EveningDetective_AddAction",
"responses": { "responses": {
"200": { "200": {
"description": "A successful response.", "description": "A successful response.",
"schema": { "schema": {
"$ref": "#/definitions/evening_detectiveDeleteTeamsRsp" "$ref": "#/definitions/evening_detectiveAddActionRsp"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/runtimeError"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/evening_detectiveAddActionReq"
}
}
],
"tags": [
"EveningDetective"
]
}
},
"/teams": {
"get": {
"operationId": "EveningDetective_GetTeams",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/evening_detectiveGetTeamsRsp"
} }
}, },
"default": { "default": {
@@ -213,14 +301,14 @@
] ]
} }
}, },
"/teams/{id}": { "/teams/pdf": {
"get": { "get": {
"operationId": "EveningDetective_GetTeam", "operationId": "EveningDetective_DownloadTeamsQrCodesFile",
"responses": { "responses": {
"200": { "200": {
"description": "A successful response.", "description": "A successful response.",
"schema": { "schema": {
"$ref": "#/definitions/evening_detectiveGetTeamRsp" "$ref": "#/definitions/evening_detectiveDownloadTeamsQrCodesFileRsp"
} }
}, },
"default": { "default": {
@@ -230,15 +318,6 @@
} }
} }
}, },
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"type": "string",
"format": "int64"
}
],
"tags": [ "tags": [
"EveningDetective" "EveningDetective"
] ]
@@ -285,27 +364,70 @@
} }
}, },
"definitions": { "definitions": {
"evening_detectiveAddActionRsp": { "GetGraphRspEdge": {
"type": "object",
"properties": {
"from": {
"type": "string"
},
"to": {
"type": "string"
},
"arrows": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"evening_detectiveAction": {
"type": "object", "type": "object",
"properties": { "properties": {
"id": { "id": {
"type": "string", "type": "string",
"format": "int64" "format": "int64"
}, },
"to": { "place": {
"type": "string"
},
"name": {
"type": "string" "type": "string"
}, },
"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"
}
} }
} }
}, },
"evening_detectiveAddActionReq": {
"type": "object",
"properties": {
"place": {
"type": "string"
}
}
},
"evening_detectiveAddActionRsp": {
"type": "object"
},
"evening_detectiveAddTeamsReq": { "evening_detectiveAddTeamsReq": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -331,13 +453,43 @@
"evening_detectiveApplication": { "evening_detectiveApplication": {
"type": "object", "type": "object",
"properties": { "properties": {
"id": {
"type": "string",
"format": "int64"
},
"name": { "name": {
"type": "string" "type": "string"
},
"state": {
"type": "string"
},
"number": {
"type": "string"
} }
} }
}, },
"evening_detectiveDeleteTeamsRsp": { "evening_detectiveDoor": {
"type": "object" "type": "object",
"properties": {
"code": {
"type": "string"
},
"name": {
"type": "string"
},
"show": {
"type": "boolean"
}
}
},
"evening_detectiveDownloadTeamsQrCodesFileRsp": {
"type": "object",
"properties": {
"result": {
"type": "string",
"format": "byte"
}
}
}, },
"evening_detectiveGameStartReq": { "evening_detectiveGameStartReq": {
"type": "object" "type": "object"
@@ -357,13 +509,55 @@
"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": {
"name": {
"type": "string"
},
"actions": { "actions": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/evening_detectiveAddActionRsp" "$ref": "#/definitions/evening_detectiveAction"
} }
} }
} }
@@ -405,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"
}, },
@@ -426,6 +674,12 @@
"name": { "name": {
"type": "string" "type": "string"
}, },
"password": {
"type": "string"
},
"url": {
"type": "string"
},
"spendTime": { "spendTime": {
"type": "string", "type": "string",
"format": "int64" "format": "int64"
@@ -453,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": {
+198 -63
View File
@@ -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,20 +15,23 @@ 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"
EveningDetective_AddTeams_FullMethodName = "/crabs.evening_detective.EveningDetective/AddTeams" EveningDetective_AddTeams_FullMethodName = "/crabs.evening_detective.EveningDetective/AddTeams"
EveningDetective_GetTeams_FullMethodName = "/crabs.evening_detective.EveningDetective/GetTeams" EveningDetective_GetTeams_FullMethodName = "/crabs.evening_detective.EveningDetective/GetTeams"
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_DeleteTeams_FullMethodName = "/crabs.evening_detective.EveningDetective/DeleteTeams" 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,11 +43,14 @@ type EveningDetectiveClient interface {
GetTeams(ctx context.Context, in *GetTeamsReq, opts ...grpc.CallOption) (*GetTeamsRsp, error) GetTeams(ctx context.Context, in *GetTeamsReq, opts ...grpc.CallOption) (*GetTeamsRsp, error)
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)
DeleteTeams(ctx context.Context, in *DeleteTeamsReq, opts ...grpc.CallOption) (*DeleteTeamsRsp, 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 {
@@ -56,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
} }
@@ -65,8 +72,9 @@ func (c *eveningDetectiveClient) Ping(ctx context.Context, in *PingReq, opts ...
} }
func (c *eveningDetectiveClient) AddTeams(ctx context.Context, in *AddTeamsReq, opts ...grpc.CallOption) (*AddTeamsRsp, error) { 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
} }
@@ -74,8 +82,9 @@ func (c *eveningDetectiveClient) AddTeams(ctx context.Context, in *AddTeamsReq,
} }
func (c *eveningDetectiveClient) GetTeams(ctx context.Context, in *GetTeamsReq, opts ...grpc.CallOption) (*GetTeamsRsp, error) { 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
} }
@@ -83,8 +92,9 @@ func (c *eveningDetectiveClient) GetTeams(ctx context.Context, in *GetTeamsReq,
} }
func (c *eveningDetectiveClient) GetTeamsCSV(ctx context.Context, in *GetTeamsCSVReq, opts ...grpc.CallOption) (*GetTeamsCSVRsp, error) { 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
} }
@@ -92,17 +102,9 @@ func (c *eveningDetectiveClient) GetTeamsCSV(ctx context.Context, in *GetTeamsCS
} }
func (c *eveningDetectiveClient) GetTeam(ctx context.Context, in *GetTeamReq, opts ...grpc.CallOption) (*GetTeamRsp, error) { 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 {
return nil, err
}
return out, nil
}
func (c *eveningDetectiveClient) DeleteTeams(ctx context.Context, in *DeleteTeamsReq, opts ...grpc.CallOption) (*DeleteTeamsRsp, error) {
out := new(DeleteTeamsRsp)
err := c.cc.Invoke(ctx, EveningDetective_DeleteTeams_FullMethodName, in, out, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -110,8 +112,19 @@ func (c *eveningDetectiveClient) DeleteTeams(ctx context.Context, in *DeleteTeam
} }
func (c *eveningDetectiveClient) AddAction(ctx context.Context, in *AddActionReq, opts ...grpc.CallOption) (*AddActionRsp, error) { 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
} }
@@ -119,8 +132,9 @@ func (c *eveningDetectiveClient) AddAction(ctx context.Context, in *AddActionReq
} }
func (c *eveningDetectiveClient) GameStart(ctx context.Context, in *GameStartReq, opts ...grpc.CallOption) (*GameStartRsp, error) { 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
} }
@@ -128,8 +142,9 @@ func (c *eveningDetectiveClient) GameStart(ctx context.Context, in *GameStartReq
} }
func (c *eveningDetectiveClient) GameStop(ctx context.Context, in *GameStopReq, opts ...grpc.CallOption) (*GameStopRsp, error) { 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
} }
@@ -137,8 +152,39 @@ func (c *eveningDetectiveClient) GameStop(ctx context.Context, in *GameStopReq,
} }
func (c *eveningDetectiveClient) GiveApplications(ctx context.Context, in *GiveApplicationsReq, opts ...grpc.CallOption) (*GiveApplicationsRsp, error) { 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
} }
@@ -147,24 +193,30 @@ 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)
GetTeams(context.Context, *GetTeamsReq) (*GetTeamsRsp, error) GetTeams(context.Context, *GetTeamsReq) (*GetTeamsRsp, error)
GetTeamsCSV(context.Context, *GetTeamsCSVReq) (*GetTeamsCSVRsp, error) GetTeamsCSV(context.Context, *GetTeamsCSVReq) (*GetTeamsCSVRsp, error)
GetTeam(context.Context, *GetTeamReq) (*GetTeamRsp, error) GetTeam(context.Context, *GetTeamReq) (*GetTeamRsp, error)
DeleteTeams(context.Context, *DeleteTeamsReq) (*DeleteTeamsRsp, 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")
@@ -181,12 +233,12 @@ func (UnimplementedEveningDetectiveServer) GetTeamsCSV(context.Context, *GetTeam
func (UnimplementedEveningDetectiveServer) GetTeam(context.Context, *GetTeamReq) (*GetTeamRsp, error) { func (UnimplementedEveningDetectiveServer) GetTeam(context.Context, *GetTeamReq) (*GetTeamRsp, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetTeam not implemented") return nil, status.Errorf(codes.Unimplemented, "method GetTeam not implemented")
} }
func (UnimplementedEveningDetectiveServer) DeleteTeams(context.Context, *DeleteTeamsReq) (*DeleteTeamsRsp, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteTeams not implemented")
}
func (UnimplementedEveningDetectiveServer) AddAction(context.Context, *AddActionReq) (*AddActionRsp, error) { 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")
} }
@@ -196,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
@@ -206,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)
} }
@@ -299,24 +368,6 @@ func _EveningDetective_GetTeam_Handler(srv interface{}, ctx context.Context, dec
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _EveningDetective_DeleteTeams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteTeamsReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(EveningDetectiveServer).DeleteTeams(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: EveningDetective_DeleteTeams_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(EveningDetectiveServer).DeleteTeams(ctx, req.(*DeleteTeamsReq))
}
return interceptor(ctx, in, info, handler)
}
func _EveningDetective_AddAction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _EveningDetective_AddAction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AddActionReq) in := new(AddActionReq)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@@ -335,6 +386,24 @@ func _EveningDetective_AddAction_Handler(srv interface{}, ctx context.Context, d
return interceptor(ctx, in, info, handler) 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 {
@@ -389,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)
@@ -416,14 +539,14 @@ var EveningDetective_ServiceDesc = grpc.ServiceDesc{
MethodName: "GetTeam", MethodName: "GetTeam",
Handler: _EveningDetective_GetTeam_Handler, Handler: _EveningDetective_GetTeam_Handler,
}, },
{
MethodName: "DeleteTeams",
Handler: _EveningDetective_DeleteTeams_Handler,
},
{ {
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,
@@ -436,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-waNsD4Su.js";const n={},c={class:"about"};function r(_,e){return a(),s("div",c,e[0]||(e[0]=[t("h1",null,"This is an about page",-1)]))}const l=o(n,[["render",r]]);export{l as default};
@@ -1 +0,0 @@
@media (min-width: 1024px){.about{min-height:100vh;display:flex;align-items:center}}
-1
View File
@@ -1 +0,0 @@
:root{--vt-c-white: #ffffff;--vt-c-white-soft: #f8f8f8;--vt-c-white-mute: #f2f2f2;--vt-c-black: #181818;--vt-c-black-soft: #222222;--vt-c-black-mute: #282828;--vt-c-indigo: #2c3e50;--vt-c-divider-light-1: rgba(60, 60, 60, .29);--vt-c-divider-light-2: rgba(60, 60, 60, .12);--vt-c-divider-dark-1: rgba(84, 84, 84, .65);--vt-c-divider-dark-2: rgba(84, 84, 84, .48);--vt-c-text-light-1: var(--vt-c-indigo);--vt-c-text-light-2: rgba(60, 60, 60, .66);--vt-c-text-dark-1: var(--vt-c-white);--vt-c-text-dark-2: rgba(235, 235, 235, .64)}:root{--color-background: var(--vt-c-white);--color-background-soft: var(--vt-c-white-soft);--color-background-mute: var(--vt-c-white-mute);--color-border: var(--vt-c-divider-light-2);--color-border-hover: var(--vt-c-divider-light-1);--color-heading: var(--vt-c-text-light-1);--color-text: var(--vt-c-text-light-1);--section-gap: 160px}@media (prefers-color-scheme: dark){:root{--color-background: var(--vt-c-black);--color-background-soft: var(--vt-c-black-soft);--color-background-mute: var(--vt-c-black-mute);--color-border: var(--vt-c-divider-dark-2);--color-border-hover: var(--vt-c-divider-dark-1);--color-heading: var(--vt-c-text-dark-1);--color-text: var(--vt-c-text-dark-2)}}*,*:before,*:after{box-sizing:border-box;margin:0;font-weight:400}body{min-height:100vh;color:var(--color-text);background:var(--color-background);transition:color .5s,background-color .5s;line-height:1.6;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:15px;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#app{max-width:1280px;margin:0 auto;padding:2rem;font-weight:400}a,.green{text-decoration:none;color:#00bd7e;transition:.4s;padding:3px}@media (hover: hover){a:hover{background-color:#00bd7e33}}@media (min-width: 1024px){body{display:flex;place-items:center}#app{display:grid;grid-template-columns:1fr 1fr;padding:0 2rem}}h1[data-v-d1bb330e]{font-weight:500;font-size:2.6rem;position:relative;top:-10px}h3[data-v-d1bb330e]{font-size:1.2rem}.greetings h1[data-v-d1bb330e],.greetings h3[data-v-d1bb330e]{text-align:center}@media (min-width: 1024px){.greetings h1[data-v-d1bb330e],.greetings h3[data-v-d1bb330e]{text-align:left}}header[data-v-4bc5e6bf]{line-height:1.5;max-height:100vh}.logo[data-v-4bc5e6bf]{display:block;margin:0 auto 2rem}nav[data-v-4bc5e6bf]{width:100%;font-size:12px;text-align:center;margin-top:2rem}nav a.router-link-exact-active[data-v-4bc5e6bf]{color:var(--color-text)}nav a.router-link-exact-active[data-v-4bc5e6bf]:hover{background-color:transparent}nav a[data-v-4bc5e6bf]{display:inline-block;padding:0 1rem;border-left:1px solid var(--color-border)}nav a[data-v-4bc5e6bf]:first-of-type{border:0}@media (min-width: 1024px){header[data-v-4bc5e6bf]{display:flex;place-items:center;padding-right:calc(var(--section-gap) / 2)}.logo[data-v-4bc5e6bf]{margin:0 2rem 0 0}header .wrapper[data-v-4bc5e6bf]{display:flex;place-items:flex-start;flex-wrap:wrap}nav[data-v-4bc5e6bf]{text-align:left;margin-left:-1rem;font-size:1rem;padding:1rem 0;margin-top:1rem}}.item[data-v-fd0742eb]{margin-top:2rem;display:flex;position:relative}.details[data-v-fd0742eb]{flex:1;margin-left:1rem}i[data-v-fd0742eb]{display:flex;place-items:center;place-content:center;width:32px;height:32px;color:var(--color-text)}h3[data-v-fd0742eb]{font-size:1.2rem;font-weight:500;margin-bottom:.4rem;color:var(--color-heading)}@media (min-width: 1024px){.item[data-v-fd0742eb]{margin-top:0;padding:.4rem 0 1rem calc(var(--section-gap) / 2)}i[data-v-fd0742eb]{top:calc(50% - 25px);left:-26px;position:absolute;border:1px solid var(--color-border);background:var(--color-background);border-radius:8px;width:50px;height:50px}.item[data-v-fd0742eb]:before{content:" ";border-left:1px solid var(--color-border);position:absolute;left:0;bottom:calc(50% + 25px);height:calc(50% - 25px)}.item[data-v-fd0742eb]:after{content:" ";border-left:1px solid var(--color-border);position:absolute;left:0;top:calc(50% + 25px);height:calc(50% - 25px)}.item[data-v-fd0742eb]:first-of-type:before{display:none}.item[data-v-fd0742eb]:last-of-type:after{display:none}}
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
@media (min-width: 1024px){.about{min-height:100vh;display:flex;align-items:center}}
-1
View File
@@ -1 +0,0 @@
import{_ as o,c as s,a as t,o as a}from"./index-BKrme-n6.js";const n={},c={class:"about"};function r(_,e){return a(),s("div",c,e[0]||(e[0]=[t("h1",null,"This is an about page",-1)]))}const l=o(n,[["render",r]]);export{l as default};
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
:root{--vt-c-white: #ffffff;--vt-c-white-soft: #f8f8f8;--vt-c-white-mute: #f2f2f2;--vt-c-black: #181818;--vt-c-black-soft: #222222;--vt-c-black-mute: #282828;--vt-c-indigo: #2c3e50;--vt-c-divider-light-1: rgba(60, 60, 60, .29);--vt-c-divider-light-2: rgba(60, 60, 60, .12);--vt-c-divider-dark-1: rgba(84, 84, 84, .65);--vt-c-divider-dark-2: rgba(84, 84, 84, .48);--vt-c-text-light-1: var(--vt-c-indigo);--vt-c-text-light-2: rgba(60, 60, 60, .66);--vt-c-text-dark-1: var(--vt-c-white);--vt-c-text-dark-2: rgba(235, 235, 235, .64)}:root{--color-background: var(--vt-c-white);--color-background-soft: var(--vt-c-white-soft);--color-background-mute: var(--vt-c-white-mute);--color-border: var(--vt-c-divider-light-2);--color-border-hover: var(--vt-c-divider-light-1);--color-heading: var(--vt-c-text-light-1);--color-text: var(--vt-c-text-light-1);--section-gap: 160px}@media (prefers-color-scheme: dark){:root{--color-background: var(--vt-c-black);--color-background-soft: var(--vt-c-black-soft);--color-background-mute: var(--vt-c-black-mute);--color-border: var(--vt-c-divider-dark-2);--color-border-hover: var(--vt-c-divider-dark-1);--color-heading: var(--vt-c-text-dark-1);--color-text: var(--vt-c-text-dark-2)}}*,*:before,*:after{box-sizing:border-box;margin:0;font-weight:400}body{min-height:100vh;color:var(--color-text);background:var(--color-background);transition:color .5s,background-color .5s;line-height:1.6;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:15px;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#app{max-width:1280px;margin:0 auto;padding:2rem;font-weight:400}a,.green{text-decoration:none;color:#00bd7e;transition:.4s;padding:3px}@media (hover: hover){a:hover{background-color:#00bd7e33}}@media (min-width: 1024px){body{display:flex;place-items:center}#app{display:grid;grid-template-columns:1fr 1fr;padding:0 2rem}}h1[data-v-d1bb330e]{font-weight:500;font-size:2.6rem;position:relative;top:-10px}h3[data-v-d1bb330e]{font-size:1.2rem}.greetings h1[data-v-d1bb330e],.greetings h3[data-v-d1bb330e]{text-align:center}@media (min-width: 1024px){.greetings h1[data-v-d1bb330e],.greetings h3[data-v-d1bb330e]{text-align:left}}header[data-v-cf9c2f5c]{line-height:1.5;max-height:100vh}.logo[data-v-cf9c2f5c]{display:block;margin:0 auto 2rem}nav[data-v-cf9c2f5c]{width:100%;font-size:12px;text-align:center;margin-top:2rem}nav a.router-link-exact-active[data-v-cf9c2f5c]{color:var(--color-text)}nav a.router-link-exact-active[data-v-cf9c2f5c]:hover{background-color:transparent}nav a[data-v-cf9c2f5c]{display:inline-block;padding:0 1rem;border-left:1px solid var(--color-border)}nav a[data-v-cf9c2f5c]:first-of-type{border:0}@media (min-width: 1024px){header[data-v-cf9c2f5c]{display:flex;place-items:center;padding-right:calc(var(--section-gap) / 2)}.logo[data-v-cf9c2f5c]{margin:0 2rem 0 0}header .wrapper[data-v-cf9c2f5c]{display:flex;place-items:flex-start;flex-wrap:wrap}nav[data-v-cf9c2f5c]{text-align:left;margin-left:-1rem;font-size:1rem;padding:1rem 0;margin-top:1rem}}.item[data-v-fd0742eb]{margin-top:2rem;display:flex;position:relative}.details[data-v-fd0742eb]{flex:1;margin-left:1rem}i[data-v-fd0742eb]{display:flex;place-items:center;place-content:center;width:32px;height:32px;color:var(--color-text)}h3[data-v-fd0742eb]{font-size:1.2rem;font-weight:500;margin-bottom:.4rem;color:var(--color-heading)}@media (min-width: 1024px){.item[data-v-fd0742eb]{margin-top:0;padding:.4rem 0 1rem calc(var(--section-gap) / 2)}i[data-v-fd0742eb]{top:calc(50% - 25px);left:-26px;position:absolute;border:1px solid var(--color-border);background:var(--color-background);border-radius:8px;width:50px;height:50px}.item[data-v-fd0742eb]:before{content:" ";border-left:1px solid var(--color-border);position:absolute;left:0;bottom:calc(50% + 25px);height:calc(50% - 25px)}.item[data-v-fd0742eb]:after{content:" ";border-left:1px solid var(--color-border);position:absolute;left:0;top:calc(50% + 25px);height:calc(50% - 25px)}.item[data-v-fd0742eb]:first-of-type:before{display:none}.item[data-v-fd0742eb]:last-of-type:after{display:none}}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB