Compare commits

68 Commits

Author SHA1 Message Date
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
83 changed files with 3755 additions and 1946 deletions
Vendored
BIN
View File
Binary file not shown.
+1
View File
@@ -29,3 +29,4 @@ store.db
cmd/text_to_story/*.txt cmd/text_to_story/*.txt
cmd/text_to_story/*.json cmd/text_to_story/*.json
data/
+1 -1
View File
@@ -17,7 +17,7 @@
"buildFlags": "-tags local" "buildFlags": "-tags local"
}, },
{ {
"name": "Local Launch", "name": "Launch for tests",
"type": "go", "type": "go",
"request": "launch", "request": "launch",
"mode": "debug", "mode": "debug",
+39 -1
View File
@@ -1,15 +1,53 @@
{ {
"cSpell.words": [ "cSpell.words": [
"ввода",
"внимательно",
"вопрос",
"второй",
"дает",
"Детективы",
"диалога",
"диалогом",
"другой",
"задать",
"корректный",
"Можно",
"Название",
"найдена",
"найдено",
"Нельзя",
"Открываем",
"Открытие",
"открытую",
"открыть",
"получение",
"после",
"правила",
"Приложение", "Приложение",
"приложением",
"проходами",
"проходом",
"скрытой",
"скрытую",
"существует",
"сходить",
"Такой",
"Текст", "Текст",
"Точка", "Точка",
"точки", "точки",
"точку",
"читайте",
"AUTOINCREMENT", "AUTOINCREMENT",
"gopdf", "gopdf",
"gwmux", "gwmux",
"localtime", "localtime",
"palces", "palces",
"qrcode", "qrcode",
"signintech" "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"
]
}
]
}
+4 -1
View File
@@ -25,4 +25,7 @@ clear:
rm ./internal/tests/store.db rm ./internal/tests/store.db
test: test:
DB_FILENAME=store.db go test -count=1 ./... 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
```
+58 -1
View File
@@ -77,6 +77,19 @@ service EveningDetective {
get: "/teams/pdf" 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 {}
@@ -126,6 +139,13 @@ message Application {
int64 id = 1; int64 id = 1;
string name = 2; string name = 2;
string state = 3; string state = 3;
string number = 4;
}
message Door {
string code = 1;
string name = 2;
bool show = 3;
} }
message GetTeamReq {} message GetTeamReq {}
@@ -146,7 +166,10 @@ message Action {
string place = 2; string place = 2;
string name = 3; string name = 3;
string text = 4; string text = 4;
repeated Application applications = 5; string image = 5;
repeated Application applications = 6;
bool hidden = 7;
repeated Door doors = 8;
} }
message GetGameReq {} message GetGameReq {}
@@ -179,3 +202,37 @@ message DownloadTeamsQrCodesFileReq {}
message DownloadTeamsQrCodesFileRsp { message DownloadTeamsQrCodesFileRsp {
bytes result = 1; 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;
repeated GraphApplication applications = 4;
}
message GraphApplication {
string name = 1;
}
+70 -11
View File
@@ -5,13 +5,21 @@ import (
"embed" "embed"
"evening_detective/internal/app" "evening_detective/internal/app"
"evening_detective/internal/config" "evening_detective/internal/config"
"evening_detective/internal/modules/cleaner"
"evening_detective/internal/modules/formatter"
"evening_detective/internal/modules/password"
"evening_detective/internal/services" "evening_detective/internal/services"
"evening_detective/internal/services/db"
"evening_detective/internal/services/link"
"evening_detective/internal/services/pdf"
"evening_detective/internal/services/story_service" "evening_detective/internal/services/story_service"
"evening_detective/internal/services/story_storage"
proto "evening_detective/proto" proto "evening_detective/proto"
"io/fs" "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"
@@ -36,22 +44,49 @@ func main() {
s := grpc.NewServer() s := grpc.NewServer()
// Attach the Greeter service to the server // Attach the Greeter service to the server
dbFilepath := config.GetDBFilepath() dbFilepath := config.GetDBFilepath()
repository, err := services.NewRepository(dbFilepath)
if err != nil {
panic(err)
}
storyFilepath := config.GetStoryFilepath() dbService, err := db.NewDBService(dbFilepath)
storyService, err := story_service.NewStoryService(storyFilepath)
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
clientHost := config.GetHost()
adminClientHost := config.GetAdminHost()
fileHost := config.GetFileHost()
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(clientHost)
passwordGenerator := password.NewPasswordGenerator()
pdfGenerator := pdf.NewPDFGenerator()
proto.RegisterEveningDetectiveServer( proto.RegisterEveningDetectiveServer(
s, s,
app.NewServer( app.NewServer(
services.NewServices( services.NewServices(
repository, dbService,
storyService, storyService,
linkService,
passwordGenerator,
pdfGenerator,
), ),
), ),
) )
@@ -107,9 +142,22 @@ func main() {
muxUser.Handle("/", fileServerUser) muxUser.Handle("/", fileServerUser)
// Serve user web server // Serve user web server
log.Println("Serving user web on http://0.0.0.0:8100") log.Println("Serving user web on http://0.0.0.0" + config.ClientPort)
go func() { go func() {
log.Fatalln(http.ListenAndServe(":8100", muxUser)) log.Fatalln(http.ListenAndServe(config.ClientPort, muxUser))
}()
go func() {
dir := "./data/story/images"
// Создаем файловый сервер
fs := http.FileServer(http.Dir(dir))
// Добавляем middleware для логирования
http.Handle("/", loggingMiddleware(fs))
log.Println("Файловый сервер запущен на http://localhost:8120")
log.Println("Обслуживается директория: " + dir)
log.Fatal(http.ListenAndServe(":8120", nil))
}() }()
muxAdmin := http.NewServeMux() muxAdmin := http.NewServeMux()
@@ -121,14 +169,14 @@ func main() {
muxAdmin.Handle("/", fileServerAdmin) muxAdmin.Handle("/", fileServerAdmin)
// Serve admin web server // Serve admin web server
log.Println("Serving admin web on http://0.0.0.0:8110") log.Printf("Serving admin web on %s\n", adminClientHost)
log.Fatalln(http.ListenAndServe(":8110", muxAdmin)) log.Fatalln(http.ListenAndServe(":8110", muxAdmin))
} }
func cors(h http.Handler) http.Handler { func cors(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, Authorization, ResponseType, X-Id, X-Password") w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, Authorization, ResponseType, X-Id, X-Password")
if r.Method == "OPTIONS" { if r.Method == "OPTIONS" {
return return
@@ -136,3 +184,14 @@ func cors(h http.Handler) http.Handler {
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
}) })
} }
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Логируем запрос
log.Printf("[%s] %s %s", r.Method, r.URL.Path, time.Since(start))
next.ServeHTTP(w, r)
})
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
:root{--vt-c-white: #ffffff;--vt-c-white-soft: #f8f8f8;--vt-c-white-mute: #f2f2f2;--vt-c-black: #181818;--vt-c-black-soft: #222222;--vt-c-black-mute: #282828;--vt-c-indigo: #2c3e50;--vt-c-divider-light-1: rgba(60, 60, 60, .29);--vt-c-divider-light-2: rgba(60, 60, 60, .12);--vt-c-divider-dark-1: rgba(84, 84, 84, .65);--vt-c-divider-dark-2: rgba(84, 84, 84, .48);--vt-c-text-light-1: var(--vt-c-indigo);--vt-c-text-light-2: rgba(60, 60, 60, .66);--vt-c-text-dark-1: var(--vt-c-white);--vt-c-text-dark-2: rgba(235, 235, 235, .64);--main-color: rgba(34, 50, 60, 1);--second-color: rgb(136, 105, 31);--main-back-color: rgba(240, 240, 240, 1);--main-back-item-color: rgba(254, 254, 254, 1)}:root{--color-background: var(--vt-c-white);--color-background-soft: var(--vt-c-white-soft);--color-background-mute: var(--vt-c-white-mute);--color-border: var(--vt-c-divider-light-2);--color-border-hover: var(--vt-c-divider-light-1);--color-heading: var(--vt-c-text-light-1);--color-text: var(--vt-c-text-light-1);--section-gap: 160px}*,*:before,*:after{box-sizing:border-box;margin:0;font-weight:400}body{min-height:100dvh;color:var(--color-text);background:var(--main-back-color);transition:color .5s,background-color .5s;line-height:1.6;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:15px;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.input-custom{width:100%;box-sizing:border-box;margin-bottom:15px}.button-custom{margin-left:auto;background-color:var(--main-color);font-weight:600;color:#fff}.button-custom-inline{margin:10px;background-color:var(--main-color);font-weight:600;color:#fff;padding:6px 8px;border:1px solid #ddd;border-radius:15px;font-size:14px}.button-custom:hover,.button-custom-inline:hover{background-color:var(--second-color)}.input-custom,.button-custom{padding:12px 16px;border:1px solid #ddd;border-radius:15px;font-size:16px}.button-container{display:flex}.center-message{display:flex;justify-content:center;align-items:center;height:calc(100dvh - 100px);text-align:center}.qr[data-v-666b8d35]{position:absolute;top:130px;right:30px;text-align:center;width:120px}.header-block[data-v-5b6894ef]{background-color:var(--main-color);font-size:large;color:#fff;vertical-align:middle;padding:10px 0 10px 16px;font-weight:700}.buttons-block[data-v-4d8d73b6]{padding-top:5px}.button-menu[data-v-4d8d73b6]{margin:5px 10px 5px 0}table[data-v-4d8d73b6]{width:700px;border-collapse:collapse;margin:30px auto;border:1px solid #444444}th[data-v-4d8d73b6],td[data-v-4d8d73b6]{padding:12px;text-align:left}th[data-v-4d8d73b6]{background-color:var(--main-color);color:#fff;font-weight:700}tr[data-v-4d8d73b6]:nth-child(odd){background-color:#efefef}tr[data-v-4d8d73b6]:nth-child(2n){background-color:#fff}tr[data-v-4d8d73b6]:hover{background-color:#cfcfcf}.time[data-v-4d8d73b6]{white-space:nowrap}.team-name[data-v-4d8d73b6]{font-weight:600}.link-button[data-v-4d8d73b6]{display:inline;border:none;background:none;padding:0;margin:0;font:inherit;cursor:pointer;color:var(--main-color);text-decoration:underline;font-weight:600;-webkit-appearance:none;-moz-appearance:none;appearance:none;line-height:inherit;text-align:left}.link-button[data-v-4d8d73b6]:hover{color:var(--second-color);text-decoration:none}.link-button[data-v-4d8d73b6]:active{color:#036}.link-button[data-v-4d8d73b6]:focus{outline:none;text-decoration:none;box-shadow:0 0 0 2px #0066cc4d}.form-block[data-v-4d8d73b6]{width:700px;margin:0 auto}a[data-v-4d8d73b6]{color:var(--second-color);text-decoration:none;transition:all .2s ease;cursor:pointer}a[data-v-4d8d73b6]:hover{text-decoration:underline;text-decoration-thickness:2px;text-underline-offset:3px}a[data-v-4d8d73b6]:focus-visible{outline:2px solid #3182ce;outline-offset:2px;border-radius:2px}a[disabled][data-v-4d8d73b6]{color:#a0aec0;pointer-events:none;cursor:not-allowed}.button-container[data-v-4d8d73b6]{margin-bottom:30px}.cell-center[data-v-4d8d73b6]{text-align:center}.three-columns[data-v-1112068b]{display:grid;grid-template-columns:1fr 2fr 1fr;gap:20px;height:100%}.column[data-v-1112068b]{border:1px solid #e0e0e0;padding:20px;margin:5px}
File diff suppressed because one or more lines are too long
@@ -5,8 +5,8 @@
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ВД Админка</title> <title>ВД Админка</title>
<script type="module" crossorigin src="/assets/index-Tp3gHO58.js"></script> <script type="module" crossorigin src="/assets/index-CH9kKe_e.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-JqNLMpQx.css"> <link rel="stylesheet" crossorigin href="/assets/index-C16dKKOO.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
Binary file not shown.
@@ -1 +0,0 @@
@media (min-width: 1024px){.about{min-height:100vh;display:flex;align-items:center}}
@@ -1 +0,0 @@
import{_ as o,c as s,a as t,o as a}from"./index-DkrD2ASU.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};
Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

@@ -1 +0,0 @@
:root{--vt-c-white: #ffffff;--vt-c-white-soft: #f8f8f8;--vt-c-white-mute: #f2f2f2;--vt-c-black: #181818;--vt-c-black-soft: #222222;--vt-c-black-mute: #282828;--vt-c-indigo: #2c3e50;--vt-c-divider-light-1: rgba(60, 60, 60, .29);--vt-c-divider-light-2: rgba(60, 60, 60, .12);--vt-c-divider-dark-1: rgba(84, 84, 84, .65);--vt-c-divider-dark-2: rgba(84, 84, 84, .48);--vt-c-text-light-1: var(--vt-c-indigo);--vt-c-text-light-2: rgba(60, 60, 60, .66);--vt-c-text-dark-1: var(--vt-c-white);--vt-c-text-dark-2: rgba(235, 235, 235, .64);--main-color: rgba(34, 50, 60, 1);--second-color: rgb(136, 105, 31);--main-back-color: rgba(240, 240, 240, 1);--main-back-item-color: rgba(254, 254, 254, 1)}:root{--color-background: var(--vt-c-white);--color-background-soft: var(--vt-c-white-soft);--color-background-mute: var(--vt-c-white-mute);--color-border: var(--vt-c-divider-light-2);--color-border-hover: var(--vt-c-divider-light-1);--color-heading: var(--vt-c-text-light-1);--color-text: var(--vt-c-text-light-1);--section-gap: 160px}*,*:before,*:after{box-sizing:border-box;margin:0;font-weight:400}body{min-height:100dvh;color:var(--color-text);background:var(--main-back-color);transition:color .5s,background-color .5s;line-height:1.6;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:15px;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.header-block{height:60px;background-color:var(--main-color);font-size:large;color:#fff;vertical-align:middle;padding:15px 0 10px 16px;font-weight:700}.input-custom{width:100%;box-sizing:border-box;margin-bottom:15px}.button-custom{margin-left:auto;background-color:var(--main-color);font-weight:600;color:#fff}.button-custom:hover{background-color:var(--main-color);opacity:.9}.button-custom:disabled{opacity:.5}.input-custom,.button-custom{padding:12px 16px;border:1px solid #ddd;border-radius:15px;font-size:16px}.button-container{display:flex}.center-message{display:flex;justify-content:center;align-items:center;height:calc(100dvh - 100px);text-align:center}header[data-v-913ef6b1]{line-height:1.5;max-height:100vh}.logo[data-v-913ef6b1]{display:block;margin:0 auto 2rem}nav[data-v-913ef6b1]{width:100%;font-size:12px;text-align:center;margin-top:2rem}nav a.router-link-exact-active[data-v-913ef6b1]{color:var(--color-text)}nav a.router-link-exact-active[data-v-913ef6b1]:hover{background-color:transparent}nav a[data-v-913ef6b1]{display:inline-block;padding:0 1rem;border-left:1px solid var(--color-border)}nav a[data-v-913ef6b1]:first-of-type{border:0}@media (min-width: 1024px){header[data-v-913ef6b1]{display:flex;place-items:center;padding-right:calc(var(--section-gap) / 2)}.logo[data-v-913ef6b1]{margin:0 2rem 0 0}header .wrapper[data-v-913ef6b1]{display:flex;place-items:flex-start;flex-wrap:wrap}nav[data-v-913ef6b1]{text-align:left;margin-left:-1rem;font-size:1rem;padding:1rem 0;margin-top:1rem}}body[data-v-5146ce3d]{overflow:hidden}.hr[data-v-5146ce3d]{margin:7px 0}.body-custom[data-v-5146ce3d]{font-size:medium}.info-custom[data-v-5146ce3d]{padding-left:15px}.logo[data-v-5146ce3d]{float:left;margin:10px}.logo-right[data-v-5146ce3d]{float:right;margin:12px}.second-color[data-v-5146ce3d]{color:var(--second-color)}.form-custom[data-v-5146ce3d]{border:1px solid #444444;background-color:var(--main-back-color);position:fixed;bottom:0;left:0;width:100%;padding:20px;color:#fff}.message-cloud[data-v-5146ce3d]{border:1px solid #444444;border-radius:15px;margin:12px 10px;padding:16px;background-color:var(--main-back-item-color)}.message-header[data-v-5146ce3d]{font-size:large;font-weight:200}.message-content[data-v-5146ce3d]{font-weight:500;white-space:pre-line}.message-footer[data-v-5146ce3d]{font-weight:400;color:var(--second-color)}.form-block[data-v-5146ce3d]{height:140px}.messages-block[data-v-5146ce3d]{height:calc(100dvh - 200px);overflow-y:auto;scrollbar-width:none}@media (min-width: 1025px){.center-block-custom[data-v-5146ce3d]{width:700px;margin:0 auto}}.center-message[data-v-5146ce3d]{height:calc(100dvh - 140px)}.qr[data-v-5146ce3d]{text-align:center;width:200px}.error-message[data-v-13746d20]{color:brown;margin:16px 0}
File diff suppressed because one or more lines are too long
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: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 66 KiB

+2 -2
View File
@@ -5,8 +5,8 @@
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Вечерний детектив</title> <title>Вечерний детектив</title>
<script type="module" crossorigin src="/assets/index-DkrD2ASU.js"></script> <script type="module" crossorigin src="/assets/index-CBvKsrC1.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BJbIgyMb.css"> <link rel="stylesheet" crossorigin href="/assets/index-BkPf0Nib.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
Binary file not shown.
-163
View File
@@ -1,163 +0,0 @@
{
"places": [
{
"code": "А",
"name": "Администрация",
"text": "Тут работают директор и старший вожатый. На столе Лехи вы находите расписание на 23 августа. Стопку книг по педагогике и какие-то записки от детей — похоже они очень любили Лёху.",
"applications": [{ "name": "Расписание дня" }]
},
{
"code": "В-1",
"name": "Вход",
"text": "Ржавые ворота с выцветшей табличкой «Добро пожаловать в «Сосновый Бор» скрипят на ветру. За ними — узкая дорога, уходящая вглубь соснового леса. На покосившемся стенде у проходной — пожелтевший плакат с информацией:\n\n\"Лагерь «Сосновый Бор» - Место, где рождаются характеры.\"\n\n«Орлы» — спортивные, загорелые, с грамотами за победы в эстафетах.\n\n«Лисы» — те, кто вместо костра сидит с книгами. Их шепотом называют «ботанами», но именно они всегда знают ответ.\n\n«Волки» — вечные нарушители. Их следы находят то на крыше столовой, то у водонапорной вышки.\n\n«Совы» — тихие художники и поэты. Их рисунки иногда находят даже в лесу.\n\nВ самом низу подпись: Директор лагеря - Виктор Сергеевич Громов."
},
{
"code": "В-2",
"name": "Водонапорная вышка",
"text": "Труп лежит на земле с разбитой головой, рядом лежит окровавленный камень, судя по всему от него и погиб старший вожатый. Помимо следов погибшего рядом с трупом вы находите отпечатки кроссовок. Они глубокие, будто кто-то бежал или резко разворачивался. Подошва – с характерным рисунком в виде зигзагов. Также вы находите другие следы двух пар ботинок. Первые – массивные, с грубым протектором. Следы ведут к телу, затем резко обрываются – будто человек замер на месте. Вторые – аккуратные, с узким носком. Они подходят к камню, а затем удаляются в сторону вышки."
},
{
"code": "Д",
"name": "Душ",
"text": "Вы дергаете дверь душа, она закрыта. Завхоз и повариха, сидящие на лавочке рассказали, что душ не работает – у кочегара голова болит уже вторые сутки, – и почему-то громко расхохотались. Поговорив с женщинами вы узнаете, что кормят в лагере очень плохо, даже 2 проверки приезжали – нарушений не нашли, но дети жалуются и почти не едят. Лёша сам ругаться приходил несколько раз, уж очень он за детей волновался."
},
{
"code": "К-1",
"name": "Клуб",
"text": "В клубе вас встречает диджей Пётр.\n“Концерт как всегда душевный, все плакали обнимались, вроде все здесь были, хотя награждение “Лучший ребенок” в этот раз Лёха проводил, а не броненосец, вот его то я вчера и не видел”.\nОн рассказывает, что дискотека прошла на ура, танцевали и пели под все самые лучшие песни. Кажется он почти не общался с Лехой и до сих пор не знает, что произошло: «А какие медляки, танцевал весь лагерь, правда Макса и Даши не было. Они у нас главные знаменитости, танцуют медляки каждый вечер, а днем делают вид что противны друг другу, думают что дети верят в их притворство»"
},
{
"code": "К-2",
"name": "Костровище",
"text": "В глубине лагеря, за последним отрядом расчищена круглая площадка, окруженная полукругом пеньков-сидушек, сколоченных из толстых спилов сосны. В центре — огромный костровой круг, выложенный из камней, почерневших от бесчисленных костров."
},
{
"code": "К-3",
"name": "Коморка физрука",
"text": "Тесное помещение, забитое спортинвентарем до самого потолка. В углу валяется порванный мат, из которого торчит пожелтевший поролон. На полках вперемешку лежат мячи разных видов – футбольные, волейбольные, баскетбольные, – половина из которых явно спущена. Воняет резиной, потом и старыми кроссовками."
},
{
"code": "Л",
"name": "Лавки",
"text": "Пара покосившихся деревянных скамеек, выкрашенных когда-то в зелёную краску, но теперь облезлых до серой древесины. Сиденья испещрены выцарапанными именами, сердечками и нецензурными словами – кто-то старательно выводил их гвоздём или кончиком ключа."
},
{
"code": "М",
"name": "Медпункт",
"text": "Небольшое побелено-голубое здание в тени сосен. Внутри – приемная с выцветшими плакатами про “чистые руки” и “опасность клещей”, изолятор с двумя койками за занавеской, а дальше – общий душ и туалет. На столе стоят 3 кружки из-под чая. Печенье “Юбилейное” в открытой пачке. Медицинская карта с последней записью: “22:30 23.08.99 – Волкова С. (отряд “Орлы”) – жалобы на температуру и тошноту. Диагноз: пищевое отравление?” В мусорном ведре вы замечаете упаковку от таблетки, 3 пакетика чая и использованный презерватив. Слабый аромат духов – дешевый, сладкий, явно не медицинский."
},
{
"code": "O-1",
"name": "Отряд 1",
"text": "Белое кирпичное здание, с выложенными кирпичом “1970”. Сбоку нарисован Чебурашка, коричневой и красной краской. Отряд опрятный, но сильно пахнет потом. Койки заправлены с армейской аккуратностью. На стене – газета с детскими стихами, где кто-то красной ручкой исправил рифмы на похабные."
},
{
"code": "O-2",
"name": "Отряд 2",
"text": "Тени от сосен за окном рисуют на стенах полосатые узоры. Зайдя внутрь здания, вы замечаете одного из вожатых — Кирилла. Заведя разговор о произошедшем, вы по секрету узнаете, что его напарница Даша бегает на свиданки с вожатым первого отряда Максимом. Больше ничего необычного вы не заметили."
},
{
"code": "O-3",
"name": "Отряд 3",
"text": "Приближаясь, Вы осматриваете кирпичное здание с нарисованным сбоку здания мультяшным героем. Переводя взгляд в окно, вы видите детей, разбившихся на группки: кто-то рисует, кто-то бегает, а кто-то просто сидит в сторонке. \n«Вы тоже за тем, что пропало?» — раздаётся голос за спиной. Обернувшись, вы видите мальчика лет 12 с слишком взрослым взглядом. «Лёха говорит, что если что — искать надо в лисах. Только он не договорил... что именно.» Он нервно оглядывается и исчезает за углом, оставив вас с новой загадкой и ощущением, что за вами уже наблюдают."
},
{
"code": "O-4",
"name": "Отряд 4",
"text": "У входа в отряд вы видите десятки пар обуви, аккуратно выставленных в ряд. Среди них вы сразу замечаете кроссовки с характерным зигзагообразным протектором, слегка запачканные грязью и... чем-то тёмным у носка. «Это Катины!» — оживляется девочка с косичками, тыча пальцем в обувь. — «Она их всегда носит, даже когда дождь!»"
},
{
"code": "П",
"name": "Площадь",
"text": "Площадь в лагере, развивается флаг России и флаг лагеря — зелёное полотно с белой сосной. Чисто выметен асфальт. Музыку здесь почти не слышно, хотя граммофон висит на ближайшем столбе."
},
{
"code": "С-1",
"name": "Столовая",
"text": "В столовой пахнет хлоркой, висит плакат чистоты. Там вы никого не нашли."
},
{
"code": "С-2",
"name": "Стадион",
"text": "На стадионе вы встречаете детей 3 и 4 отрядов. Вы интересуетесь, почему они не собирают вещи. Вам рассказывают, что их вожатые самые классные на земле, они приучили их к спорту — каждое утро они даже бегали с Катей вокруг стадиона и водонапорной вышки. Но сегодня последний день и Катя почему-то отправила их играть в волейбол, а бегать запретила."
},
{
"code": "Т",
"name": "Туалет",
"text": "За туалетом вы находите пачку сигарет и записку как у Лехи, размер и бумага совпадают. На ней написано «Сегодня вам сильно повезет, не сдавайся и все получится!» Похоже кто-то раздавал печенье с предсказанием.",
"applications": [{ "name": "Газета" }]
},
{
"code": "Ц",
"name": "Цветы",
"text": "Неровный овал, огороженный потрёпанными синими бордюрами, которые когда-то были яркими, но теперь выцвели под солнцем и покрылись трещинами. Земля в одних местах усыпана мелкими камушками, в других – потрескалась от жары, будто жаждет воды. Но вопреки всему здесь цветут бархатцы – жёлтые и оранжевые, как маленькие огоньки."
},
{
"code": "МК",
"name": "Макс Крутов",
"text": "Перед вами парень в рваных джинсах и черной футболке, похожий на музыканта. Говорит, что был в душе вчера во время дискотеки: «Тёма был на дискотеке, а наши все вчера на медляках отжигали. Ну, я и решил помыться. Пока в душ шёл у администрации у Лехи сигарету и стрельнул. Поговорили немного, о чем я вам не могу сказать. Потом мы заметили за туалетом какие-то шорохи, Леха решил проверить, сказал, что за одно и обход сделает, якобы лишним не будет, ответственный наш» — Пока он это рассказывал мимо проходил директор — «Смотрите наш броненосец пиджак скинул, а я думал это его кожа!»."
},
{
"code": "АК",
"name": "Артём Ковалёв",
"text": "«Я следил за детьми в клубе, даже драку девочек разнял — ребята утром подслушали ссору Лехи с Алиной и поддерживали разные стороны, как видите очень яро. Макс отпросился в душ, с парнями вчера спортом был занят весь день»."
},
{
"code": "ДО",
"name": "Даша Орлова",
"text": "«Вчера весь вечер я сидела с детьми которые не пошли на дискотеку. Но те ребята, которые могли это подтвердить, уже уехали домой»."
},
{
"code": "КЛ",
"name": "Кирилл Лебедев",
"text": "«Леху на втором ужине только видел, да и то он мимо прошел. Мы вчера с Аней, Катей и Темой дежурили на дискотеке. Потом сразу пошли на костер, это могла быть самая лучшая смена. Я рассказывал много историй вчера на костре и про историю лагеря и легенды разные. На свечку мы ушли в отряды — вспоминали смену, делились впечатлениями. На улицу больше не выходили, там похолодало, да и не видно уже ничего было — слишком поздно»."
},
{
"code": "АГ",
"name": "Артём Глушко",
"text": "Артём сидит и читает книгу в своем отряде, попутно помогает ребятам собирать чемоданы. Он интересуется, удалось ли что-то узнать, рассказывает, что они с Лехой как-то застали Макса за кражей денег из кассы, и с тех пор в их отношениях была напряженность. Артем предложил вам печенье и пошел дальше помогать ребятам."
},
{
"code": "АС",
"name": "Анна Соколова",
"text": "«Лёха был ответственным человеком и всегда помогал, иногда он делал больше чем от него требовалось. Он мог и веселые старты провести, когда физрук ленится, вёл все мероприятия лагеря со сцены, встречал проверки. Мне кажется, он некоторые проверки даже устраивал сам, чтобы лагерь лучше делать. Директор даже на него скидывал какие-то бумажные дела. Лёха был очень начитанный хоть и учился на математика, любила с ним поболтать»."
},
{
"code": "КС",
"name": "Катя Светлова",
"text": "На диване в центре общей комнаты отряда вы встречаете молодую девушку в яркой оранжевой футболке с принтами, шортах и белых носках. Длинные волосы, собранные в небрежный хвост или косу, минимум макияжа. Она сидит в обнимку со старшими мальчиками отряда, смеётся и рассказывает им какую-то историю."
},
{
"code": "АЗ",
"name": "Алина Зайцева",
"text": "Вы находите ее рядом с турниками. Девушка спортивного телосложения сидит на траве, прикрыв лицо капюшоном. Слезы бегут по ее лицу. “Мы встречались, хотели даже пожениться, он последнее время очень злой ходил, эта смена его совсем из колеи выбила. Рассказывать он не хотел, знаю что долго за документами засиживался уже когда все спали”."
},
{
"code": "ВСГ",
"name": "Виктор Сергеевич Громов",
"text": "«Труп обнаружил охранник Виктор Петрович на ночном обходе, позвал меня. Я проверил пульс и позвонил в полицию. Вот вам список работников лагеря. Страшно осознавать что кто-то из них может быть убийцей».",
"applications": [{ "name": "Список работников лагеря" }]
},
{
"code": "ЕО",
"name": "Елена Орлова",
"text": "Очень красивая статная девушка в белом халате встречает вас нежной улыбкой. \n«Во время дискотеки я была в приемной, королевская ночь по статистике самая травмоопасная. Хотя на удивление только одна девочка с температурой, я выдала таблетку и вожатая увела её обратно в отряд»."
},
{
"code": "СС",
"name": "Сергей Смирнов",
"text": "Вы подошли к мужчине среднего роста в спортивном костюме: «Вечером телевизор смотрел, музыка долбила спать не давала. Петрович заходил, мы досмотрели “музыкальный ринг” да и побёг он. На дискотеки я не ходок, да и Лёха сказал помощь не нужна. За день набегался, дел много и не только своих, там помоги, сям помоги, никто ничего не может, вот и помогаю. Устаю, возраст как никак. Петрович попросил, вчера помочь с вывозом мусора, с тех пор в коморке и сидел. Лёху видел последний раз в воскресенье, он сказал зарядку не проводить пущай дети поспят»."
},
{
"code": "ВПБ",
"name": "Виктор Петрович Белов",
"text": "«У меня свой режим: завтрак в 9 утра, вынос мусора в 9 вечера, в 11 вечера обход. Всю дискотеку Лёха сидел в администрации, как с концерта пришел, так и не выходил. В пол десятого я до клуба отходил проверить все ли спокойно во время дискотеки, проверил все и за клубом, площадь посмотрел — минут 30 заняло. Фонари давно у клуба не работают, пришлось с фонариком везде лазить. Тело обнаружил уже на обходе — очень перепугался и сразу в администрацию побежал».",
"applications": [{ "name": "Карта лагеря" }]
},
{
"code": "ВД",
"name": "Вечерний детектив",
"text": "Дело №1 “Последний костёр”\n\nАвтор сценария: \nВладимир Фёдоров\n\nРедакторы:\nДарья Лисовая\nЕкатерина Бокова\nЕкатерина Бутина\nАнастасия Пушкарёва\n\nОзвучка:\nАлексей Демченко\n\nХудожники:\nВладимир Фёдоров\nАлина Заугольникова\n\nРазработчик:\nВладимир Фёдоров\n\nМы желаем вам приятной игры!"
}
]
}
+19 -10
View File
@@ -1,17 +1,24 @@
module evening_detective module evening_detective
go 1.23 go 1.23.0
toolchain go1.23.10 toolchain go1.24.5
require ( require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2
google.golang.org/grpc v1.64.0 google.golang.org/grpc v1.75.0
) )
require ( require (
github.com/ghodss/yaml v1.0.0 // indirect
github.com/golang/glog v1.2.5 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 // indirect github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 // indirect
github.com/pkg/errors v0.8.1 // indirect github.com/pkg/errors v0.8.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
gopkg.in/yaml.v2 v2.2.3 // indirect
) )
require ( require (
@@ -27,10 +34,12 @@ require (
github.com/mattn/go-sqlite3 v1.14.28 github.com/mattn/go-sqlite3 v1.14.28
github.com/signintech/gopdf v0.32.0 github.com/signintech/gopdf v0.32.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
golang.org/x/net v0.23.0 // indirect golang.org/x/net v0.41.0 // indirect
golang.org/x/sys v0.26.0 // indirect golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.15.0 // indirect golang.org/x/text v0.28.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
google.golang.org/protobuf v1.34.1 google.golang.org/protobuf v1.36.7 // indirect
) )
tool github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway
+8
View File
@@ -63,3 +63,11 @@ func (s *Server) GiveApplications(ctx context.Context, req *proto.GiveApplicatio
func (s *Server) DownloadTeamsQrCodesFile(ctx context.Context, req *proto.DownloadTeamsQrCodesFileReq) (*proto.DownloadTeamsQrCodesFileRsp, error) { func (s *Server) DownloadTeamsQrCodesFile(ctx context.Context, req *proto.DownloadTeamsQrCodesFileReq) (*proto.DownloadTeamsQrCodesFileRsp, error) {
return s.services.DownloadTeamsQrCodesFile(ctx, req) 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)
}
+97 -11
View File
@@ -1,19 +1,105 @@
package config package config
import "os" import (
"net"
"os"
"path/filepath"
)
const (
ClientPort = ":8100"
AdminClientPort = ":8110"
FilePort = ":8120"
)
func GetStoryFilepath() string { func GetStoryFilepath() string {
storyFilename := os.Getenv("STORY_FILENAME") return getFilepath("STORY_FILENAME", "data/story/story.json")
if storyFilename != "" {
return storyFilename
}
return "./data/story/story.json"
} }
func GetDBFilepath() string { func GetDBFilepath() string {
storyFilename := os.Getenv("DB_FILENAME") return getFilepath("DB_FILENAME", "data/db/store.db")
if storyFilename != "" { }
return storyFilename
} func GetAdminHost() string {
return "data/db/store.db" 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 GetHost() string {
host := os.Getenv("HOST")
if host != "" {
return host
}
ips, err := getLocalIPs()
if err != nil || len(ips) == 0 {
return "http://127.0.0.1" + ClientPort
}
return "http://" + ips[0] + ClientPort
}
func GetFileHost() string {
host := os.Getenv("FILE_HOST")
if host != "" {
return host
}
ips, err := getLocalIPs()
if err != nil || len(ips) == 0 {
return "http://127.0.0.1" + FilePort
}
return "http://" + ips[0] + FilePort
}
func getFilepath(env string, defaultFilepath string) string {
filepath := selectFilepath(env, defaultFilepath)
ensureDirExists(filepath)
return filepath
}
func selectFilepath(env string, defaultFilepath string) string {
filepath := os.Getenv(env)
if filepath != "" {
return filepath
}
return defaultFilepath
}
func ensureDirExists(filePath string) error {
dir := filepath.Dir(filePath)
if dir == "" || dir == "." || dir == "/" {
return nil
}
return os.MkdirAll(dir, 0755)
}
func getLocalIPs() ([]string, error) {
var ips []string
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil, err
}
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok {
continue
}
ip := ipNet.IP
if ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() {
continue
}
if ipv4 := ip.To4(); ipv4 != nil {
ips = append(ips, ipv4.String())
}
}
return ips, nil
} }
+1 -42
View File
@@ -1,49 +1,8 @@
package models package models
import (
"fmt"
"net"
"net/url"
)
type Team struct { type Team struct {
ID int64 ID int64
Name string Name string
Password string Password string
} Link string
func (t *Team) GetTeamUrl() (string, error) {
ips, err := getLocalIPs()
if err != nil {
return "", err
}
ip := ips[0]
u := fmt.Sprintf("http://%s:8100?name=%s&password=%s", ip, url.PathEscape(t.Name), t.Password)
return u, nil
}
func getLocalIPs() ([]string, error) {
var ips []string
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil, err
}
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok {
continue
}
ip := ipNet.IP
if ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() {
continue
}
if ipv4 := ip.To4(); ipv4 != nil {
ips = append(ips, ipv4.String())
}
}
return ips, nil
} }
+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)
}
})
}
}
+5
View File
@@ -0,0 +1,5 @@
package formatter
type IFormatter interface {
FormatText(text string) string
}
+42
View File
@@ -0,0 +1,42 @@
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()
}
@@ -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
}
@@ -6,8 +6,14 @@ var (
letters = []rune("abcdefghijklmnopqrstuvwxyz123456789") letters = []rune("abcdefghijklmnopqrstuvwxyz123456789")
) )
func GenPass(n int) string { type service struct{}
b := make([]rune, n)
func NewPasswordGenerator() IPasswordGenerator {
return &service{}
}
func (s *service) GeneratePassword(length int) string {
b := make([]rune, length)
for i := range b { for i := range b {
b[i] = letters[rand.Intn(len(letters))] b[i] = letters[rand.Intn(len(letters))]
} }
+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)
}
@@ -1,4 +1,4 @@
package services package db
import ( import (
"context" "context"
@@ -10,11 +10,11 @@ import (
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
) )
type Repository struct { type service struct {
db *sql.DB db *sql.DB
} }
func NewRepository(filepath string) (*Repository, error) { func NewDBService(filepath string) (IDBService, error) {
db, err := sql.Open("sqlite3", filepath) db, err := sql.Open("sqlite3", filepath)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -36,15 +36,15 @@ func NewRepository(filepath string) (*Repository, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Repository{db: db}, nil return &service{db: db}, nil
} }
func (r *Repository) Close() { func (s *service) Close() {
r.db.Close() s.db.Close()
} }
func (r *Repository) GetTeams(ctx context.Context) ([]*models.Team, error) { func (s *service) GetTeams(ctx context.Context) ([]*models.Team, error) {
rows, err := r.db.Query("select id, name, password from teams") rows, err := s.db.Query("select id, name, password from teams")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -62,51 +62,8 @@ func (r *Repository) GetTeams(ctx context.Context) ([]*models.Team, error) {
return teams, nil return teams, nil
} }
func (r *Repository) AddTeams(ctx context.Context, teams []*models.Team) ([]*models.Team, error) { func (s *service) GetTeam(ctx context.Context, teamId any, password any) (*models.Team, error) {
for _, team := range teams { rows, err := s.db.Query("select id, name from teams where LOWER(name) = LOWER($1) and password = $2", teamId, password)
result, err := r.db.Exec("insert into teams (name, password) values ($1, $2)", team.Name, team.Password)
if err != nil {
return nil, err
}
team.ID, err = result.LastInsertId()
if err != nil {
return nil, err
}
}
return teams, nil
}
func (r *Repository) GetActions(ctx context.Context, teamId int64) ([]*models.Action, error) {
rows, err := r.db.Query("select id, place from actions where teamId = $1", teamId)
if err != nil {
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 (r *Repository) AddActions(ctx context.Context, teamId int64, actions []*models.Action) error {
for _, action := range actions {
_, err := r.db.Exec("insert into actions (place, teamId) values ($1, $2)", action.Place, teamId)
if err != nil {
return err
}
}
return nil
}
func (r *Repository) GetTeam(ctx context.Context, teamId any, password any) (*models.Team, error) {
rows, err := r.db.Query("select id, name from teams where LOWER(name) = LOWER($1) and password = $2", teamId, password)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -127,9 +84,47 @@ func (r *Repository) GetTeam(ctx context.Context, teamId any, password any) (*mo
return teams[0], nil return teams[0], nil
} }
func (r *Repository) AddApplications(ctx context.Context, teamId int64, applications []*models.Application) error { func (s *service) AddTeams(ctx context.Context, teams []*models.Team) ([]*models.Team, error) {
for _, application := range applications { for _, team := range teams {
_, err := r.db.Exec("insert into applications (name, teamId, state) values ($1, $2, $3)", application.Name, teamId, application.State) 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 { if err != nil {
return err return err
} }
@@ -137,8 +132,8 @@ func (r *Repository) AddApplications(ctx context.Context, teamId int64, applicat
return nil return nil
} }
func (r *Repository) GetApplications(ctx context.Context, teamId int64) ([]*models.Application, error) { func (s *service) GetApplications(ctx context.Context, teamId int64) ([]*models.Application, error) {
rows, err := r.db.Query("select id, name from applications where teamId = $1", teamId) rows, err := s.db.Query("select id, name from applications where teamId = $1", teamId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -156,8 +151,8 @@ func (r *Repository) GetApplications(ctx context.Context, teamId int64) ([]*mode
return applications, nil return applications, nil
} }
func (r *Repository) GetApplicationsByState(ctx context.Context, teamId int64, state string) ([]*models.Application, error) { func (s *service) GetApplicationsByState(ctx context.Context, teamId int64, state string) ([]*models.Application, error) {
rows, err := r.db.Query("select id, name from applications where teamId = $1 and state = $2", teamId, state) rows, err := s.db.Query("select id, name from applications where teamId = $1 and state = $2", teamId, state)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -175,9 +170,9 @@ func (r *Repository) GetApplicationsByState(ctx context.Context, teamId int64, s
return applications, nil return applications, nil
} }
func (r *Repository) GiveApplications(ctx context.Context, teamId int64, applications []*models.Application) error { func (s *service) AddApplications(ctx context.Context, teamId int64, applications []*models.Application) error {
for _, application := range applications { for _, application := range applications {
_, err := r.db.Exec("update applications set state = \"gave\" where teamId = $1 and id = $2", teamId, application.ID) _, err := s.db.Exec("insert into applications (name, teamId, state) values ($1, $2, $3)", application.Name, teamId, application.State)
if err != nil { if err != nil {
return err return err
} }
@@ -185,8 +180,18 @@ func (r *Repository) GiveApplications(ctx context.Context, teamId int64, applica
return nil return nil
} }
func (r *Repository) GetGame(ctx context.Context) (*models.Game, error) { func (s *service) GiveApplications(ctx context.Context, teamId int64, applications []*models.Application) error {
rows, err := r.db.Query("select state, startAt, endAt from games limit 1") 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 { if err != nil {
return nil, err return nil, err
} }
@@ -202,7 +207,7 @@ func (r *Repository) GetGame(ctx context.Context) (*models.Game, error) {
} }
state := "NEW" state := "NEW"
_, err = r.db.Exec("insert into games (state, startAt, endAt) values ($1, '', '')", state) _, err = s.db.Exec("insert into games (state, startAt, endAt) values ($1, '', '')", state)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -210,27 +215,22 @@ func (r *Repository) GetGame(ctx context.Context) (*models.Game, error) {
return game, nil return game, nil
} }
func (r *Repository) GameUpdateState(ctx context.Context, state string) error { func (s *service) UpdateGameState(ctx context.Context, state string) error {
game, err := r.GetGame(ctx) game, err := s.GetGame(ctx)
if err != nil { if err != nil {
return err return err
} }
switch state { switch state {
case "RUN": case "RUN":
if game.StartTime == "" { if game.StartTime == "" {
_, err := r.db.Exec("update games set state = $1, startAt = datetime('now', 'localtime')", state) _, err := s.db.Exec("update games set state = $1, startAt = datetime('now', 'localtime')", state)
return err return err
} }
_, err := r.db.Exec("update games set state = $1", state) _, err := s.db.Exec("update games set state = $1", state)
return err return err
case "STOP": case "STOP":
_, err := r.db.Exec("update games set state = $1, endAt = datetime('now', 'localtime')", state) _, err := s.db.Exec("update games set state = $1, endAt = datetime('now', 'localtime')", state)
return err return err
} }
return nil return nil
} }
func (r *Repository) DeleteAllTeams(ctx context.Context) error {
_, err := r.db.Exec("delete from teams where 1")
return err
}
+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)
}
+15 -7
View File
@@ -2,7 +2,7 @@ package services
import ( import (
"evening_detective/internal/models" "evening_detective/internal/models"
"evening_detective/internal/services/story_service" story_service_models "evening_detective/internal/services/story_service/models"
"evening_detective/proto" "evening_detective/proto"
"strings" "strings"
) )
@@ -29,20 +29,28 @@ func mapProtoTeamsToTeam(team *proto.Team) *models.Team {
} }
} }
func mapActionToProtoAction(action *models.Action) *proto.Action { func mapPlaceToProtoAction(place *story_service_models.Place) *proto.Action {
return &proto.Action{ return &proto.Action{
Id: action.ID, Place: place.Code,
Place: action.Place,
} }
} }
func mapStoryApplicationToProtoApplication(application *story_service.Application) *proto.Application { func mapStoryApplicationToProtoApplication(application *story_service_models.Application) *proto.Application {
return &proto.Application{ return &proto.Application{
Name: application.Name, Name: application.Name,
Number: application.Number,
} }
} }
func mapStoryApplicationsToApplications(applications []*story_service.Application) []*models.Application { func mapStoryDoorToProtoDoor(door *story_service_models.Door) *proto.Door {
return &proto.Door{
Code: door.Code,
Name: door.Name,
Show: door.Show,
}
}
func mapStoryApplicationsToApplications(applications []*story_service_models.Application) []*models.Application {
res := make([]*models.Application, 0, len(applications)) res := make([]*models.Application, 0, len(applications))
for _, application := range applications { for _, application := range applications {
res = append(res, mapStoryApplicationToApplication(application)) res = append(res, mapStoryApplicationToApplication(application))
@@ -50,7 +58,7 @@ func mapStoryApplicationsToApplications(applications []*story_service.Applicatio
return res return res
} }
func mapStoryApplicationToApplication(application *story_service.Application) *models.Application { func mapStoryApplicationToApplication(application *story_service_models.Application) *models.Application {
return &models.Application{ return &models.Application{
Name: application.Name, Name: application.Name,
State: "NEW", State: "NEW",
+7
View File
@@ -0,0 +1,7 @@
package pdf
import "evening_detective/internal/models"
type IPDFGenerator interface {
CreateTeamsPDF(teams []*models.Team) ([]byte, error)
}
@@ -1,12 +1,11 @@
package pdf_service package pdf
import ( import (
"bytes" "bytes"
"embed"
"evening_detective/internal/models" "evening_detective/internal/models"
"strings" "strings"
"embed"
"github.com/signintech/gopdf" "github.com/signintech/gopdf"
"github.com/skip2/go-qrcode" "github.com/skip2/go-qrcode"
) )
@@ -14,7 +13,13 @@ import (
//go:embed JetBrainsMono-Medium.ttf //go:embed JetBrainsMono-Medium.ttf
var f embed.FS var f embed.FS
func CreateTeamsPdf(teams []*models.Team) ([]byte, error) { type service struct{}
func NewPDFGenerator() IPDFGenerator {
return &service{}
}
func (s *service) CreateTeamsPDF(teams []*models.Team) ([]byte, error) {
pdf := &gopdf.GoPdf{} pdf := &gopdf.GoPdf{}
pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4}) // W: 595, H: 842 pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4}) // W: 595, H: 842
file, err := f.Open("JetBrainsMono-Medium.ttf") file, err := f.Open("JetBrainsMono-Medium.ttf")
@@ -40,10 +45,6 @@ func CreateTeamsPdf(teams []*models.Team) ([]byte, error) {
y := (padding + 15) + yDelta*float64(i%countOnPage/3) y := (padding + 15) + yDelta*float64(i%countOnPage/3)
x := padding + xDelta*float64(i%3) x := padding + xDelta*float64(i%3)
url, err := team.GetTeamUrl()
if err != nil {
return nil, err
}
if err := printTextCenter(pdf, "Подключите Wi-Fi", xDelta-6, x+3, y); err != nil { if err := printTextCenter(pdf, "Подключите Wi-Fi", xDelta-6, x+3, y); err != nil {
return nil, err return nil, err
} }
@@ -53,7 +54,7 @@ func CreateTeamsPdf(teams []*models.Team) ([]byte, error) {
if err := printTextCenter(pdf, "Пароль: 12345678", xDelta-6, x+3, 30+y); err != nil { if err := printTextCenter(pdf, "Пароль: 12345678", xDelta-6, x+3, 30+y); err != nil {
return nil, err return nil, err
} }
if err := printQR(pdf, url, x+21, 65+y); err != nil { if err := printQR(pdf, team.Link, x+21, 65+y); err != nil {
return nil, err return nil, err
} }
if err := printTextCenter(pdf, "Войдите в приложение по qr", xDelta-6, x+3, 55+y); err != nil { if err := printTextCenter(pdf, "Войдите в приложение по qr", xDelta-6, x+3, 55+y); err != nil {
+111 -30
View File
@@ -6,7 +6,9 @@ import (
"encoding/json" "encoding/json"
"evening_detective/internal/models" "evening_detective/internal/models"
"evening_detective/internal/modules/password" "evening_detective/internal/modules/password"
"evening_detective/internal/services/pdf_service" "evening_detective/internal/services/db"
"evening_detective/internal/services/link"
"evening_detective/internal/services/pdf"
"evening_detective/internal/services/story_service" "evening_detective/internal/services/story_service"
"evening_detective/proto" "evening_detective/proto"
"fmt" "fmt"
@@ -18,30 +20,39 @@ import (
) )
type Services struct { type Services struct {
repository *Repository dbService db.IDBService
storyService *story_service.StoryService storyService *story_service.StoryService
linkService link.ILinkService
passwordGenerator password.IPasswordGenerator
pdfGenerator pdf.IPDFGenerator
} }
func NewServices( func NewServices(
repository *Repository, dbService db.IDBService,
storyService *story_service.StoryService, storyService *story_service.StoryService,
linkService link.ILinkService,
passwordGenerator password.IPasswordGenerator,
pdfGenerator pdf.IPDFGenerator,
) *Services { ) *Services {
return &Services{ return &Services{
repository: repository, dbService: dbService,
storyService: storyService, storyService: storyService,
linkService: linkService,
passwordGenerator: passwordGenerator,
pdfGenerator: pdfGenerator,
} }
} }
func (s *Services) GiveApplications(ctx context.Context, req *proto.GiveApplicationsReq) (*proto.GiveApplicationsRsp, error) { func (s *Services) GiveApplications(ctx context.Context, req *proto.GiveApplicationsReq) (*proto.GiveApplicationsRsp, error) {
applications := mapProtoApplicationsToApplications(req.Applications) applications := mapProtoApplicationsToApplications(req.Applications)
if err := s.repository.GiveApplications(ctx, req.TeamId, applications); err != nil { if err := s.dbService.GiveApplications(ctx, req.TeamId, applications); err != nil {
return nil, status.Errorf(codes.Internal, err.Error()) return nil, status.Errorf(codes.Internal, err.Error())
} }
return &proto.GiveApplicationsRsp{}, nil return &proto.GiveApplicationsRsp{}, nil
} }
func (s *Services) GetGame(ctx context.Context, _ *proto.GetGameReq) (*proto.GetGameRsp, error) { func (s *Services) GetGame(ctx context.Context, _ *proto.GetGameReq) (*proto.GetGameRsp, error) {
game, err := s.repository.GetGame(ctx) game, err := s.dbService.GetGame(ctx)
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, err.Error()) return nil, status.Errorf(codes.Internal, err.Error())
} }
@@ -53,14 +64,14 @@ func (s *Services) GetGame(ctx context.Context, _ *proto.GetGameReq) (*proto.Get
} }
func (s *Services) GameStart(ctx context.Context, _ *proto.GameStartReq) (*proto.GameStartRsp, error) { func (s *Services) GameStart(ctx context.Context, _ *proto.GameStartReq) (*proto.GameStartRsp, error) {
if err := s.repository.GameUpdateState(ctx, "RUN"); err != nil { if err := s.dbService.UpdateGameState(ctx, "RUN"); err != nil {
return nil, status.Errorf(codes.Internal, err.Error()) return nil, status.Errorf(codes.Internal, err.Error())
} }
return &proto.GameStartRsp{}, nil return &proto.GameStartRsp{}, nil
} }
func (s *Services) GameStop(ctx context.Context, req *proto.GameStopReq) (*proto.GameStopRsp, error) { func (s *Services) GameStop(ctx context.Context, req *proto.GameStopReq) (*proto.GameStopRsp, error) {
if err := s.repository.GameUpdateState(ctx, "STOP"); err != nil { if err := s.dbService.UpdateGameState(ctx, "STOP"); err != nil {
return nil, status.Errorf(codes.Internal, err.Error()) return nil, status.Errorf(codes.Internal, err.Error())
} }
return &proto.GameStopRsp{}, nil return &proto.GameStopRsp{}, nil
@@ -79,10 +90,10 @@ func (s *Services) AddAction(ctx context.Context, req *proto.AddActionReq) (*pro
Applications: mapStoryApplicationsToApplications(place.Applications), Applications: mapStoryApplicationsToApplications(place.Applications),
}, },
} }
if err := s.repository.AddActions(ctx, team.ID, actions); err != nil { if err := s.dbService.AddActions(ctx, team.ID, actions); err != nil {
return nil, status.Errorf(codes.Internal, err.Error()) return nil, status.Errorf(codes.Internal, err.Error())
} }
currentApplications, err := s.repository.GetApplications(ctx, team.ID) currentApplications, err := s.dbService.GetApplications(ctx, team.ID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -102,7 +113,7 @@ func (s *Services) AddAction(ctx context.Context, req *proto.AddActionReq) (*pro
} }
} }
if err := s.repository.AddApplications(ctx, team.ID, newApplications); err != nil { if err := s.dbService.AddApplications(ctx, team.ID, newApplications); err != nil {
return nil, status.Errorf(codes.Internal, err.Error()) return nil, status.Errorf(codes.Internal, err.Error())
} }
addLog(team, "add action", actions) addLog(team, "add action", actions)
@@ -115,20 +126,31 @@ func (s *Services) GetTeam(ctx context.Context, req *proto.GetTeamReq) (*proto.G
return nil, err return nil, err
} }
actions, err := s.repository.GetActions(ctx, team.ID) actions, err := s.dbService.GetActions(ctx, team.ID)
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, err.Error()) return nil, status.Errorf(codes.Internal, err.Error())
} }
res := make([]*proto.Action, 0, len(actions)) actionsCodes := make([]string, 0, len(actions))
for _, action := range actions { for _, action := range actions {
newAction := mapActionToProtoAction(action) actionsCodes = append(actionsCodes, action.Place)
place := s.storyService.GetPlace(action.Place) }
newAction.Text = place.Text
res := make([]*proto.Action, 0, len(actions))
for i, place := range s.storyService.GetPlaces(actionsCodes) {
newAction := mapPlaceToProtoAction(place)
newAction.Id = actions[i].ID
newAction.Name = place.Name newAction.Name = place.Name
newAction.Text = place.Text
newAction.Image = place.Image
newAction.Applications = make([]*proto.Application, 0, len(place.Applications)) newAction.Applications = make([]*proto.Application, 0, len(place.Applications))
for _, application := range place.Applications { for _, application := range place.Applications {
newAction.Applications = append(newAction.Applications, mapStoryApplicationToProtoApplication(application)) newAction.Applications = append(newAction.Applications, mapStoryApplicationToProtoApplication(application))
} }
newAction.Hidden = place.Hidden
newAction.Doors = make([]*proto.Door, 0, len(place.Doors))
for _, door := range place.Doors {
newAction.Doors = append(newAction.Doors, mapStoryDoorToProtoDoor(door))
}
res = append(res, newAction) res = append(res, newAction)
} }
return &proto.GetTeamRsp{ return &proto.GetTeamRsp{
@@ -142,23 +164,20 @@ func (s *Services) GetTeamsCSV(ctx context.Context, req *proto.GetTeamsCSVReq) (
} }
func (s *Services) GetTeams(ctx context.Context, _ *proto.GetTeamsReq) (*proto.GetTeamsRsp, error) { func (s *Services) GetTeams(ctx context.Context, _ *proto.GetTeamsReq) (*proto.GetTeamsRsp, error) {
teams, err := s.repository.GetTeams(ctx) teams, err := s.dbService.GetTeams(ctx)
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, err.Error()) return nil, status.Errorf(codes.Internal, err.Error())
} }
res := make([]*proto.TeamAdvanced, 0, len(teams)) res := make([]*proto.TeamAdvanced, 0, len(teams))
for _, team := range teams { for _, team := range teams {
newTeam := mapTeamsToTeamAdvanced(team) newTeam := mapTeamsToTeamAdvanced(team)
actions, err := s.repository.GetActions(ctx, team.ID) actions, err := s.dbService.GetActions(ctx, team.ID)
if err != nil {
return nil, err
}
newTeam.Url, err = team.GetTeamUrl()
if err != nil { if err != nil {
return nil, err return nil, err
} }
newTeam.Url = s.linkService.GetTeamClientLink(team.Name, team.Password)
newTeam.SpendTime = int64(len(actions)) newTeam.SpendTime = int64(len(actions))
currentApplications, err := s.repository.GetApplicationsByState(ctx, team.ID, "NEW") currentApplications, err := s.dbService.GetApplicationsByState(ctx, team.ID, "NEW")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -172,10 +191,10 @@ func (s *Services) AddTeams(ctx context.Context, req *proto.AddTeamsReq) (*proto
inTeams := make([]*models.Team, 0, len(req.Teams)) inTeams := make([]*models.Team, 0, len(req.Teams))
for _, team := range req.Teams { for _, team := range req.Teams {
t := mapProtoTeamsToTeam(team) t := mapProtoTeamsToTeam(team)
t.Password = password.GenPass(8) t.Password = s.passwordGenerator.GeneratePassword(8)
inTeams = append(inTeams, t) inTeams = append(inTeams, t)
} }
teams, err := s.repository.AddTeams(ctx, inTeams) teams, err := s.dbService.AddTeams(ctx, inTeams)
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, err.Error()) return nil, status.Errorf(codes.Internal, err.Error())
} }
@@ -183,23 +202,85 @@ func (s *Services) AddTeams(ctx context.Context, req *proto.AddTeamsReq) (*proto
for _, team := range teams { for _, team := range teams {
res = append(res, mapTeamsToTeamFull(team)) res = append(res, mapTeamsToTeamFull(team))
} }
return &proto.AddTeamsRsp{Teams: res}, err return &proto.AddTeamsRsp{Teams: res}, nil
} }
func (s *Services) DownloadTeamsQrCodesFile(ctx context.Context, req *proto.DownloadTeamsQrCodesFileReq) (*proto.DownloadTeamsQrCodesFileRsp, error) { func (s *Services) DownloadTeamsQrCodesFile(ctx context.Context, req *proto.DownloadTeamsQrCodesFileReq) (*proto.DownloadTeamsQrCodesFileRsp, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel() defer cancel()
teams, err := s.repository.GetTeams(ctx) teams, err := s.dbService.GetTeams(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
b, err := pdf_service.CreateTeamsPdf(teams) for _, team := range teams {
team.Link = s.linkService.GetTeamClientLink(team.Name, team.Password)
}
b, err := s.pdfGenerator.CreateTeamsPDF(teams)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &proto.DownloadTeamsQrCodesFileRsp{Result: b}, nil 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,
},
)
}
node := &story_service.GraphNode{
Code: req.Node.Code,
Name: req.Node.Name,
Text: req.Node.Text,
Applications: applications,
}
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,
},
)
}
nodes = append(nodes, &proto.GraphNode{
Code: node.Code,
Name: node.Name,
Text: node.Text,
Applications: applications,
})
}
edges := make([]*proto.GetGraphRsp_Edge, 0, len(graph.Edges))
for _, edge := range graph.Edges {
edges = append(edges, &proto.GetGraphRsp_Edge{
From: edge.From,
To: edge.To,
Arrows: "to",
Type: edge.Type,
})
}
return &proto.GetGraphRsp{
Nodes: nodes,
Edges: edges,
CountNodes: int32(len(nodes)),
CountEdges: int32(len(edges)),
}, nil
}
func (s *Services) getTeam(ctx context.Context) (*models.Team, error) { func (s *Services) getTeam(ctx context.Context) (*models.Team, error) {
md, ok := metadata.FromIncomingContext(ctx) md, ok := metadata.FromIncomingContext(ctx)
if !ok { if !ok {
@@ -221,7 +302,7 @@ func (s *Services) getTeam(ctx context.Context) (*models.Team, error) {
} }
password := passwordArr[0] password := passwordArr[0]
team, err := s.repository.GetTeam(ctx, teamId, password) team, err := s.dbService.GetTeam(ctx, teamId, password)
if err != nil { if err != nil {
return nil, status.Errorf(codes.Unauthenticated, err.Error()) return nil, status.Errorf(codes.Unauthenticated, err.Error())
} }
@@ -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,23 @@
package story_service
type Graph struct {
Nodes []*GraphNode
Edges []*GraphEdge
}
type GraphNode struct {
Code string
Name string
Text string
Applications []*GraphApplication
}
type GraphEdge struct {
From string
To string
Type string
}
type GraphApplication struct {
Name string
}
@@ -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"`
}
+252 -62
View File
@@ -1,83 +1,273 @@
package story_service package story_service
import ( import (
"encoding/json" "context"
"fmt" "evening_detective/internal/modules/cleaner"
"log" "evening_detective/internal/modules/formatter"
"os" "evening_detective/internal/services/link"
"evening_detective/internal/services/story_service/models"
"regexp"
"strings" "strings"
) )
var (
replaceMap = map[string]string{
"a": "а",
"e": "е",
"o": "о",
"c": "с",
"p": "р",
"x": "х",
"y": "у",
"k": "к",
"m": "м",
"t": "т",
"h": "н",
"b": "в",
"u": "и",
}
)
type Story struct {
Places []*Place `json:"places"`
}
type Place struct {
Code string `json:"code"`
Name string `json:"name"`
Text string `json:"text"`
Applications []*Application `json:"applications"`
}
type Application struct {
Name string `json:"name"`
}
type StoryService struct { type StoryService struct {
story *Story cleaner cleaner.ICleaner
formatter formatter.IFormatter
story *models.Story
storyStorage IStoryStorage
linkService link.ILinkService
} }
func NewStoryService(filepath string) (*StoryService, error) { func NewStoryService(
data, err := os.ReadFile(filepath) cleaner cleaner.ICleaner,
if err != nil { formatter formatter.IFormatter,
return nil, fmt.Errorf("story file %s not found", filepath) storyStorage IStoryStorage,
linkService link.ILinkService,
) (*StoryService, error) {
s := &StoryService{
cleaner: cleaner,
formatter: formatter,
storyStorage: storyStorage,
linkService: linkService,
} }
log.Printf("load story from: %s", filepath) story, err := s.storyStorage.Load(context.Background())
story := &Story{} if err != nil {
if err := json.Unmarshal(data, story); err != nil {
return nil, err return nil, err
} }
return &StoryService{story: story}, nil s.story = story
return s, nil
} }
func (s *StoryService) GetPlace(code string) *Place { func (s *StoryService) Update(ctx context.Context) error {
code = clearCode(code) if err := s.storyStorage.Save(ctx, s.story); err != nil {
return err
}
story, err := s.storyStorage.Load(ctx)
if err != nil {
return err
}
s.story = story
return nil
}
func (s *StoryService) GetPlace(code string) *models.Place {
if strings.HasPrefix(code, "[") || strings.HasSuffix(code, "]") {
return models.NewClientErrorPlace(code)
}
clearCode := s.cleaner.ClearCode(code)
for _, place := range s.story.Places { for _, place := range s.story.Places {
if clearCode(place.Code) == code { if s.cleaner.ClearCode(place.Code) == clearCode {
return place applications := make([]*models.Application, 0, len(place.Applications))
for _, application := range place.Applications {
applications = append(
applications,
models.NewApplication(
s.cleaner.ClearText(application.Name),
),
)
}
doors := make([]*models.Door, 0, len(place.Doors))
for _, door := range place.Doors {
doors = append(
doors,
models.NewDoor(
door.Code,
door.Name,
models.WithDoorShow(door.Show),
),
)
}
return models.NewPlace(
place.Code,
place.Name,
s.cleaner.ClearText(place.Text),
models.WithPlaceImage(s.linkService.GetImageLink(place.Image)),
models.WithPlaceApplication(applications...),
models.WithPlaceHidden(place.Hidden),
models.WithPlaceDoors(doors...),
)
} }
} }
return &Place{ return models.NewNotFoundPlace(code)
Code: code, }
Name: "Не найдено",
Text: "Такой точки не существует.", func (s *StoryService) GetPlaces(codes []string) []*models.Place {
places := make([]*models.Place, 0, 100)
mOpen := map[string]any{}
mDeleted := map[string]any{}
applicationNumber := 1
applicationsMap := make(map[string]interface{}, 10)
for i, code := range codes {
place := s.GetPlace(code)
for i, application := range place.Applications {
if _, ok := applicationsMap[application.Name]; ok {
place.Applications = append(place.Applications[:i], place.Applications[i+1:]...)
if len(place.Applications) == 0 {
place.Applications = nil
}
}
applicationsMap[application.Name] = struct{}{}
application.SetNumber(applicationNumber)
applicationNumber++
}
if _, ok := mOpen[place.Code]; place.Hidden && !ok {
place = models.NewNotFoundPlace(place.Code)
}
places = append(places, place)
for j, door := range places[i].Doors {
if _, ok := mDeleted[door.Code]; ok {
places[i].Doors[j].Show = false
continue
}
mOpen[door.Code] = struct{}{}
}
if i > 0 {
for j, door := range places[i-1].Doors {
if door.Code != place.Code && door.Show {
places[i-1].Doors[j].Show = false
delete(mOpen, door.Code)
mDeleted[door.Code] = struct{}{}
}
}
}
}
return places
}
func (s *StoryService) UpdatePlace(ctx context.Context, code string, node *GraphNode) error {
if code != "" && node.Code == "" {
return s.deletePlace(ctx, code)
}
if code == "" && node.Code != "" {
return s.addPlace(ctx, node)
}
if code == "" || node.Code == "" {
return nil
}
return s.updatePlace(ctx, code, node)
}
func (s *StoryService) deletePlace(ctx context.Context, code string) error {
for i := range s.story.Places {
if s.story.Places[i].Code == code {
s.story.Places = append(s.story.Places[:i], s.story.Places[i+1:]...)
break
}
}
return s.Update(ctx)
}
func (s *StoryService) addPlace(ctx context.Context, node *GraphNode) error {
s.story.Places = append(
s.story.Places,
models.NewPlace(
node.Code,
node.Name,
s.formatter.FormatText(node.Text),
models.WithPlaceApplication(s.getApplications(node)...),
),
)
return s.Update(ctx)
}
func (s *StoryService) updatePlace(ctx context.Context, code string, node *GraphNode) error {
for i := range s.story.Places {
if s.story.Places[i].Code == code {
s.story.Places[i] = models.NewPlace(
node.Code,
node.Name,
s.formatter.FormatText(node.Text),
models.WithPlaceApplication(s.getApplications(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,
node.Name,
s.formatter.FormatText(node.Text),
models.WithPlaceApplication(s.getApplications(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: application.Name,
},
)
}
return nodeApplications
}
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,
},
)
}
nodes = append(
nodes, &GraphNode{
Code: place.Code,
Name: place.Name,
Text: place.Text,
Applications: applications,
},
)
}
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",
},
)
}
}
}
return &Graph{
Nodes: nodes,
Edges: edges,
} }
} }
func clearCode(code string) string { func (s *StoryService) findPlaceLinksInText(text string) []string {
code = strings.ToLower(code) re := regexp.MustCompile(`\(\[[a-zA-Zа-яА-Я\d-]+\]\)`)
code = strings.TrimSpace(code) return re.FindAllString(text, -1)
code = strings.ReplaceAll(code, "-", "")
for latin, cyrillic := range replaceMap {
code = strings.ReplaceAll(code, latin, cyrillic)
}
return code
} }
@@ -0,0 +1,508 @@
package story_service_test
import (
"evening_detective/internal/modules/cleaner"
"evening_detective/internal/modules/formatter"
"evening_detective/internal/services/link"
"evening_detective/internal/services/story_service"
"evening_detective/internal/services/story_service/models"
"evening_detective/internal/services/story_storage"
"testing"
"github.com/stretchr/testify/assert"
)
func TestStoryService_GetPlace(t *testing.T) {
tests := []struct {
name string
story *models.Story
code string
want *models.Place
}{
{
name: "не корректный ввода",
story: &models.Story{},
code: "[Ы]",
want: models.NewClientErrorPlace("[Ы]"),
},
{
name: "точка не найдена",
story: &models.Story{},
code: "Ы",
want: models.NewNotFoundPlace("Ы"),
},
{
name: "получение точки",
story: &models.Story{
Places: []*models.Place{
models.NewPlace("Ы", "Название", "Текст"),
},
},
code: "Ы",
want: models.NewPlace("Ы", "Название", "Текст"),
},
{
name: "получение скрытой точки",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
},
},
code: "Ы",
want: models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
},
{
name: "получение точки с приложением",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceApplication(
models.NewApplication("Приложение"),
),
),
},
},
code: "Ы",
want: models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceApplication(
models.NewApplication("Приложение"),
),
),
},
{
name: "получение точки с проходом",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Й", "Приложение"),
),
),
},
},
code: "Ы",
want: models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Й", "Приложение"),
),
),
},
{
name: "получение точки с диалогом",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor(
"Й",
"Приложение",
models.WithDoorShow(true),
),
),
),
},
},
code: "Ы",
want: models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor(
"Й",
"Приложение",
models.WithDoorShow(true),
),
),
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := story_service.NewStoryService(
cleaner.NewCleaner(),
formatter.NewFormatter(),
story_storage.NewVarStoryStorage(tt.story),
link.NewLinkService("http://localhost:8120"),
)
if err != nil {
t.Fatalf("could not construct receiver type: %v", err)
}
got := s.GetPlace(tt.code)
assert.Equal(t, got, tt.want)
})
}
}
func TestStoryService_GetPlaces(t *testing.T) {
tests := []struct {
name string
story *models.Story
codes []string
want []*models.Place
}{
{
name: "Можно сходить в открытую точку",
story: &models.Story{
Places: []*models.Place{
models.NewPlace("Ы", "Название", "Текст"),
},
},
codes: []string{"Ы"},
want: []*models.Place{
models.NewPlace("Ы", "Название", "Текст"),
},
},
{
name: "Нельзя открыть скрытую точку",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
},
},
codes: []string{"Ы"},
want: []*models.Place{
models.NewNotFoundPlace("Ы"),
},
},
{
name: "Открываем скрытую точку",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название"),
models.NewDoor("Ы-3", "Название"),
),
),
models.NewPlace(
"Ы-2",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewPlace(
"Ы-3",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
},
},
codes: []string{"Ы-1", "Ы-2", "Ы-3"},
want: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название"),
models.NewDoor("Ы-3", "Название"),
),
),
models.NewPlace(
"Ы-2",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewPlace(
"Ы-3",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
},
},
{
name: "Открываем скрытую точку диалога",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
),
),
models.NewPlace(
"Ы-2",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewPlace(
"Ы-3",
"Название",
"Текст",
),
},
},
codes: []string{"Ы-1", "Ы-3", "Ы-2"},
want: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название", models.WithDoorShow(false)),
),
),
models.NewPlace(
"Ы-3",
"Название",
"Текст",
),
models.NewNotFoundPlace("Ы-2"),
},
},
{
name: "Открываем скрытую точку диалога",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
models.NewDoor("Ы-3", "Название", models.WithDoorShow(true)),
),
),
models.NewPlace(
"Ы-2",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewPlace(
"Ы-3",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
},
},
codes: []string{"Ы-1", "Ы-2", "Ы-3"},
want: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
models.NewDoor("Ы-3", "Название", models.WithDoorShow(false)),
),
),
models.NewPlace(
"Ы-2",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewNotFoundPlace("Ы-3"),
},
},
{
name: "Открываем скрытую точку после диалога",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
models.NewDoor("Ы-3", "Название", models.WithDoorShow(true)),
models.NewDoor("Ы-4", "Название"),
),
),
models.NewPlace(
"Ы-2",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewPlace(
"Ы-3",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewPlace(
"Ы-4",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
},
},
codes: []string{"Ы-1", "Ы-2", "Ы-3", "Ы-4"},
want: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
models.NewDoor("Ы-3", "Название", models.WithDoorShow(false)),
models.NewDoor("Ы-4", "Название"),
),
),
models.NewPlace(
"Ы-2",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewNotFoundPlace("Ы-3"),
models.NewPlace(
"Ы-4",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
},
},
{
name: "Открытие второй раз точки не дает задать другой вопрос",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
models.NewDoor("Ы-3", "Название", models.WithDoorShow(true)),
),
),
models.NewPlace(
"Ы-2",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewPlace(
"Ы-3",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
},
},
codes: []string{"Ы-1", "Ы-2", "Ы-3", "Ы-1", "Ы-3", "Ы-2"},
want: []*models.Place{
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название", models.WithDoorShow(true)),
models.NewDoor("Ы-3", "Название", models.WithDoorShow(false)),
),
),
models.NewPlace(
"Ы-2",
"Название",
"Текст",
models.WithPlaceHidden(true),
),
models.NewNotFoundPlace("Ы-3"),
models.NewPlace(
"Ы-1",
"Название",
"Текст",
models.WithPlaceDoors(
models.NewDoor("Ы-2", "Название", models.WithDoorShow(false)),
models.NewDoor("Ы-3", "Название", models.WithDoorShow(false)),
),
),
models.NewNotFoundPlace("Ы-3"),
models.NewNotFoundPlace("Ы-2"),
},
},
{
name: "Улики не повторяются",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceApplication(
models.NewApplication("Название"),
),
),
},
},
codes: []string{"Ы", "Ы"},
want: []*models.Place{
models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceApplication(
models.NewApplication(
"Название",
models.WithApplicationNumber(1),
),
),
),
models.NewPlace(
"Ы",
"Название",
"Текст",
),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := story_service.NewStoryService(
cleaner.NewCleaner(),
formatter.NewFormatter(),
story_storage.NewVarStoryStorage(tt.story),
link.NewLinkService("http://localhost:8120"),
)
assert.Nil(t, err)
got := s.GetPlaces(tt.codes)
assert.Equal(t, got, tt.want)
})
}
}
@@ -0,0 +1,46 @@
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
}
return story, nil
}
func (s *fileService) Save(ctx context.Context, story *models.Story) error {
data, err := json.Marshal(story)
if err != nil {
return err
}
if err := os.WriteFile(s.filepath, data, 0644); err != nil {
return err
}
log.Printf("save story to: %s", s.filepath)
return nil
}
@@ -0,0 +1,11 @@
package story_storage
import (
"context"
"evening_detective/internal/services/story_service/models"
)
type IStoryStorage interface {
Load(ctx context.Context) (*models.Story, error)
Save(ctx context.Context, story *models.Story) error
}
@@ -0,0 +1,26 @@
package story_storage
import (
"context"
"evening_detective/internal/services/story_service"
"evening_detective/internal/services/story_service/models"
)
type varService struct {
story *models.Story
}
func NewVarStoryStorage(story *models.Story) story_service.IStoryStorage {
return &varService{
story: story,
}
}
func (s *varService) Load(ctx context.Context) (*models.Story, error) {
return s.story, nil
}
func (s *varService) Save(ctx context.Context, story *models.Story) error {
s.story = story
return nil
}
-33
View File
@@ -1,33 +0,0 @@
package tests
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetApplication(t *testing.T) {
defer deleteTeams(t)
client, close := getClient()
defer close()
createTeamResp, err := createTeam(client, "Тестовая команда")
assert.Nil(t, err, "запрос отправлену спешно")
team := createTeamResp.Teams[0]
addAction(t, client, team.Name, team.Password, "Т-1")
getTeamsResp, err := getTeams(client)
assert.Nil(t, err, "запрос отправлен успешно")
applications := getTeamsResp.Teams[0].Applications
assert.Equal(t, 1, len(applications), "выдать 1 приложение")
addAction(t, client, team.Name, team.Password, "Т-1")
getTeamsResp, err = getTeams(client)
assert.Nil(t, err, "запрос отправлен успешно")
applications = getTeamsResp.Teams[0].Applications
assert.Equal(t, 1, len(applications), "выдать 1 приложение")
}
-81
View File
@@ -1,81 +0,0 @@
package tests
import (
"context"
"encoding/base64"
"log"
"testing"
"time"
"evening_detective/internal/config"
"evening_detective/internal/services"
pb "evening_detective/proto"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
func getClient() (pb.EveningDetectiveClient, func() error) {
conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure())
if err != nil {
log.Fatalf("Не удалось подключиться к серверу: %v", err)
}
return pb.NewEveningDetectiveClient(conn), conn.Close
}
func getContext() (context.Context, context.CancelFunc) {
return context.WithTimeout(context.Background(), time.Second)
}
func deleteTeams(t *testing.T) {
dbFilepath := config.GetDBFilepath()
repository, err := services.NewRepository(dbFilepath)
if err != nil {
assert.Nil(t, err, "подключение к базе")
}
defer repository.Close()
err = repository.DeleteAllTeams(context.Background())
assert.Nil(t, err, "команды удалены")
}
func createTeam(client pb.EveningDetectiveClient, name string) (*pb.AddTeamsRsp, error) {
ctx, cancel := getContext()
defer cancel()
req := &pb.AddTeamsReq{
Teams: []*pb.Team{
{Name: name},
},
}
return client.AddTeams(ctx, req)
}
func getTeams(client pb.EveningDetectiveClient) (*pb.GetTeamsRsp, error) {
ctx, cancel := getContext()
defer cancel()
req := &pb.GetTeamsReq{}
return client.GetTeams(ctx, req)
}
func addAction(
t *testing.T,
client pb.EveningDetectiveClient,
name string,
password string,
place string,
) {
ctx, cancel := getContext()
defer cancel()
md := metadata.Pairs(
"team-id", base64.StdEncoding.EncodeToString([]byte(name)),
"password", password,
)
ctx = metadata.NewOutgoingContext(ctx, md)
req := &pb.AddActionReq{
Place: place,
}
_, err := client.AddAction(ctx, req)
assert.Nil(t, err, "запрос отправлен успешно")
}
-23
View File
@@ -1,23 +0,0 @@
package tests
import (
"log"
"testing"
pb "evening_detective/proto"
)
func TestPing(t *testing.T) {
client, close := getClient()
defer close()
ctx, cancel := getContext()
defer cancel()
req := &pb.PingReq{}
_, err := client.Ping(ctx, req)
if err != nil {
log.Fatalf("Ошибка выполнения запроса: %v", err)
}
}
-10
View File
@@ -1,10 +0,0 @@
{
"places": [
{
"code": "Т-1",
"name": "Точка 1",
"text": "Текст точки 1",
"applications": [{ "name": "application 1" }]
}
]
}
-79
View File
@@ -1,79 +0,0 @@
package tests
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCreateTeam(t *testing.T) {
defer deleteTeams(t)
client, close := getClient()
defer close()
createTeamResp, err := createTeam(client, "Тестовая команда")
assert.Nil(t, err, "запрос отправлен успешно")
assert.Equal(t, len(createTeamResp.Teams), 1, "количество команд равно 1")
assert.Equal(t, createTeamResp.Teams[0].Name, "Тестовая команда")
getTeamsResp, err := getTeams(client)
assert.Nil(t, err, "запрос отправлен успешно")
assert.Equal(t, len(getTeamsResp.Teams), 1, "количество команд равно 1")
assert.Equal(t, getTeamsResp.Teams[0].Name, "Тестовая команда")
}
func TestCreateTeamWithEmptyName(t *testing.T) {
defer deleteTeams(t)
client, close := getClient()
defer close()
_, err := createTeam(client, "")
assert.NotNil(t, err, "запрос не удался")
}
func TestCreateTwoTeam(t *testing.T) {
defer deleteTeams(t)
client, close := getClient()
defer close()
_, err := createTeam(client, "Тестовая команда 1")
assert.Nil(t, err, "запрос отправлен успешно")
_, err = createTeam(client, "Тестовая команда 2")
assert.Nil(t, err, "запрос отправлен успешно")
getTeamsResp, err := getTeams(client)
assert.Nil(t, err, "запрос отправлен успешно")
assert.Equal(t, len(getTeamsResp.Teams), 2, "количество команд равно 2")
assert.Equal(t, getTeamsResp.Teams[0].Name, "Тестовая команда 1")
assert.Equal(t, getTeamsResp.Teams[1].Name, "Тестовая команда 2")
}
func TestCreateTwoEqTeam(t *testing.T) {
defer deleteTeams(t)
client, close := getClient()
defer close()
_, err := createTeam(client, "Тестовая команда")
assert.Nil(t, err, "запрос отправлен успешно")
_, err = createTeam(client, "Тестовая команда")
assert.NotNil(t, err, "запрос не удался")
}
func TestCreateTwoBadTeam(t *testing.T) {
defer deleteTeams(t)
client, close := getClient()
defer close()
_, err := createTeam(client, "Тестовая команда")
assert.Nil(t, err, "запрос отправлен успешно")
_, err = createTeam(client, "Тестовая команда ")
assert.NotNil(t, err, "запрос не удался")
}
+795 -769
View File
File diff suppressed because it is too large Load Diff
+316 -310
View File
File diff suppressed because it is too large Load Diff
+167
View File
@@ -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",
@@ -310,6 +364,23 @@
} }
}, },
"definitions": { "definitions": {
"GetGraphRspEdge": {
"type": "object",
"properties": {
"from": {
"type": "string"
},
"to": {
"type": "string"
},
"arrows": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"evening_detectiveAction": { "evening_detectiveAction": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -326,11 +397,23 @@
"text": { "text": {
"type": "string" "type": "string"
}, },
"image": {
"type": "string"
},
"applications": { "applications": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/evening_detectiveApplication" "$ref": "#/definitions/evening_detectiveApplication"
} }
},
"hidden": {
"type": "boolean"
},
"doors": {
"type": "array",
"items": {
"$ref": "#/definitions/evening_detectiveDoor"
}
} }
} }
}, },
@@ -379,6 +462,23 @@
}, },
"state": { "state": {
"type": "string" "type": "string"
},
"number": {
"type": "string"
}
}
},
"evening_detectiveDoor": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"name": {
"type": "string"
},
"show": {
"type": "boolean"
} }
} }
}, },
@@ -423,6 +523,31 @@
} }
} }
}, },
"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": {
@@ -474,6 +599,34 @@
"evening_detectiveGiveApplicationsRsp": { "evening_detectiveGiveApplicationsRsp": {
"type": "object" "type": "object"
}, },
"evening_detectiveGraphApplication": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"evening_detectiveGraphNode": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"name": {
"type": "string"
},
"text": {
"type": "string"
},
"applications": {
"type": "array",
"items": {
"$ref": "#/definitions/evening_detectiveGraphApplication"
}
}
}
},
"evening_detectivePingRsp": { "evening_detectivePingRsp": {
"type": "object" "type": "object"
}, },
@@ -528,6 +681,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": {
+117 -19
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,8 +15,8 @@ import (
// This is a compile-time assertion to ensure that this generated file // This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against. // is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later. // Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion7 const _ = grpc.SupportPackageIsVersion9
const ( const (
EveningDetective_Ping_FullMethodName = "/crabs.evening_detective.EveningDetective/Ping" EveningDetective_Ping_FullMethodName = "/crabs.evening_detective.EveningDetective/Ping"
@@ -30,6 +30,8 @@ const (
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_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.
@@ -47,6 +49,8 @@ type EveningDetectiveClient interface {
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) 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 {
@@ -58,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
} }
@@ -67,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
} }
@@ -76,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
} }
@@ -85,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
} }
@@ -94,8 +102,9 @@ func (c *eveningDetectiveClient) GetTeamsCSV(ctx context.Context, in *GetTeamsCS
} }
func (c *eveningDetectiveClient) GetTeam(ctx context.Context, in *GetTeamReq, opts ...grpc.CallOption) (*GetTeamRsp, error) { func (c *eveningDetectiveClient) GetTeam(ctx context.Context, in *GetTeamReq, opts ...grpc.CallOption) (*GetTeamRsp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetTeamRsp) out := new(GetTeamRsp)
err := c.cc.Invoke(ctx, EveningDetective_GetTeam_FullMethodName, in, out, opts...) err := c.cc.Invoke(ctx, EveningDetective_GetTeam_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -103,8 +112,9 @@ func (c *eveningDetectiveClient) GetTeam(ctx context.Context, in *GetTeamReq, op
} }
func (c *eveningDetectiveClient) AddAction(ctx context.Context, in *AddActionReq, opts ...grpc.CallOption) (*AddActionRsp, error) { func (c *eveningDetectiveClient) AddAction(ctx context.Context, in *AddActionReq, opts ...grpc.CallOption) (*AddActionRsp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(AddActionRsp) out := new(AddActionRsp)
err := c.cc.Invoke(ctx, EveningDetective_AddAction_FullMethodName, in, out, opts...) err := c.cc.Invoke(ctx, EveningDetective_AddAction_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -112,8 +122,9 @@ func (c *eveningDetectiveClient) AddAction(ctx context.Context, in *AddActionReq
} }
func (c *eveningDetectiveClient) GetGame(ctx context.Context, in *GetGameReq, opts ...grpc.CallOption) (*GetGameRsp, error) { func (c *eveningDetectiveClient) GetGame(ctx context.Context, in *GetGameReq, opts ...grpc.CallOption) (*GetGameRsp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetGameRsp) out := new(GetGameRsp)
err := c.cc.Invoke(ctx, EveningDetective_GetGame_FullMethodName, in, out, opts...) err := c.cc.Invoke(ctx, EveningDetective_GetGame_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -121,8 +132,9 @@ func (c *eveningDetectiveClient) GetGame(ctx context.Context, in *GetGameReq, op
} }
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
} }
@@ -130,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
} }
@@ -139,8 +152,9 @@ 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 { if err != nil {
return nil, err return nil, err
} }
@@ -148,8 +162,29 @@ func (c *eveningDetectiveClient) GiveApplications(ctx context.Context, in *GiveA
} }
func (c *eveningDetectiveClient) DownloadTeamsQrCodesFile(ctx context.Context, in *DownloadTeamsQrCodesFileReq, opts ...grpc.CallOption) (*DownloadTeamsQrCodesFileRsp, error) { func (c *eveningDetectiveClient) DownloadTeamsQrCodesFile(ctx context.Context, in *DownloadTeamsQrCodesFileReq, opts ...grpc.CallOption) (*DownloadTeamsQrCodesFileRsp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(DownloadTeamsQrCodesFileRsp) out := new(DownloadTeamsQrCodesFileRsp)
err := c.cc.Invoke(ctx, EveningDetective_DownloadTeamsQrCodesFile_FullMethodName, in, out, opts...) 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
} }
@@ -158,7 +193,7 @@ func (c *eveningDetectiveClient) DownloadTeamsQrCodesFile(ctx context.Context, i
// 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)
@@ -171,12 +206,17 @@ type EveningDetectiveServer interface {
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) 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")
@@ -211,7 +251,14 @@ func (UnimplementedEveningDetectiveServer) GiveApplications(context.Context, *Gi
func (UnimplementedEveningDetectiveServer) DownloadTeamsQrCodesFile(context.Context, *DownloadTeamsQrCodesFileReq) (*DownloadTeamsQrCodesFileRsp, error) { func (UnimplementedEveningDetectiveServer) DownloadTeamsQrCodesFile(context.Context, *DownloadTeamsQrCodesFileReq) (*DownloadTeamsQrCodesFileRsp, error) {
return nil, status.Errorf(codes.Unimplemented, "method DownloadTeamsQrCodesFile not implemented") 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
@@ -221,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)
} }
@@ -422,6 +476,42 @@ func _EveningDetective_DownloadTeamsQrCodesFile_Handler(srv interface{}, ctx con
return interceptor(ctx, in, info, handler) 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)
@@ -473,6 +563,14 @@ var EveningDetective_ServiceDesc = grpc.ServiceDesc{
MethodName: "DownloadTeamsQrCodesFile", MethodName: "DownloadTeamsQrCodesFile",
Handler: _EveningDetective_DownloadTeamsQrCodesFile_Handler, 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",