Compare commits

..

18 Commits

Author SHA1 Message Date
VLADIMIR af7ad13ca4 up 2026-06-02 22:05:51 +07:00
VLADIMIR 3b6d82077f update design 2026-05-29 09:34:00 +07:00
VLADIMIR 6f4c5a3aa7 update clients 2026-05-29 09:05:24 +07:00
VLADIMIR 544fac2a76 add link doors 2026-05-29 09:03:46 +07:00
VLADIMIR 813493ffa7 add format string 2026-05-29 08:48:51 +07:00
VLADIMIR 235faddaa8 fix update place 2026-05-29 08:11:45 +07:00
VLADIMIR bed126bd85 add all field to admin panel 2026-05-29 07:38:46 +07:00
VLADIMIR 26f5760987 up 2026-05-17 17:15:33 +07:00
VLADIMIR 0c987d39b3 ups 2026-05-17 16:09:33 +07:00
VLADIMIR ae63de364f ups script 2026-05-17 14:30:48 +07:00
VLADIMIR 2f5dd75460 fix 2026-05-17 11:56:46 +07:00
VLADIMIR 7f88fc6b0f fix 2026-05-16 15:23:17 +07:00
VLADIMIR b8b6d86393 fix 2026-05-16 14:43:10 +07:00
VLADIMIR ae20c57ecb update building 2026-05-16 11:43:01 +07:00
VLADIMIR f23a0c2152 update routes 2026-05-16 11:10:36 +07:00
VLADIMIR f1c69c0f1f up go 2026-05-16 00:50:26 +07:00
VLADIMIR 8c2dd57e08 updates 2026-05-11 11:45:08 +07:00
VLADIMIR a107faa366 fix 2026-05-10 22:23:43 +07:00
48 changed files with 514 additions and 239 deletions
Vendored
BIN
View File
Binary file not shown.
+5
View File
@@ -5,11 +5,13 @@
"вопрос", "вопрос",
"второй", "второй",
"дает", "дает",
"Дело",
"Детективы", "Детективы",
"диалога", "диалога",
"диалогом", "диалогом",
"другой", "другой",
"задать", "задать",
"запущен",
"корректный", "корректный",
"Можно", "Можно",
"Название", "Название",
@@ -27,6 +29,7 @@
"приложением", "приложением",
"проходами", "проходами",
"проходом", "проходом",
"сервер",
"скрытой", "скрытой",
"скрытую", "скрытую",
"существует", "существует",
@@ -38,10 +41,12 @@
"точку", "точку",
"читайте", "читайте",
"AUTOINCREMENT", "AUTOINCREMENT",
"GOARCH",
"gopdf", "gopdf",
"gwmux", "gwmux",
"localtime", "localtime",
"palces", "palces",
"protoc",
"qrcode", "qrcode",
"signintech", "signintech",
"stretchr" "stretchr"
+1 -1
View File
@@ -1,5 +1,5 @@
FROM alpine:latest FROM alpine:latest
RUN apk add --no-cache ca-certificates RUN apk add --no-cache ca-certificates
COPY bin/evening_detective /usr/local/bin/evening_detective COPY evening_detective_linux_arm64 /usr/local/bin/evening_detective
RUN chmod +x /usr/local/bin/evening_detective RUN chmod +x /usr/local/bin/evening_detective
CMD ["/usr/local/bin/evening_detective"] CMD ["/usr/local/bin/evening_detective"]
+2
View File
@@ -0,0 +1,2 @@
FROM golang:1.26-alpine
RUN apk add --no-cache gcc musl-dev
+16 -2
View File
@@ -12,10 +12,24 @@ generate:
run: run:
go run ./cmd/evening_detective/main.go go run ./cmd/evening_detective/main.go
build: build-builder:
docker build -f Dockerfile.builder -t my-go-builder .
build-macos:
rm -rf bin rm -rf bin
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -o bin/evening_detective_macos_arm64 cmd/evening_detective/main.go CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -o bin/evening_detective_macos_arm64 cmd/evening_detective/main.go
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -o bin/evening_detective_macos_amd64 cmd/evening_detective/main.go cp bin/evening_detective_macos_arm64 ../evening_detective_stories
ls ../evening_detective_stories | grep Дело | xargs -I {} cp -f bin/evening_detective_macos_arm64 "../evening_detective_stories/{}/game/"
build-linux:
docker run --rm \
-v "$$PWD":/app \
-w /app \
my-go-builder sh -c \
"CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -o bin/evening_detective_linux_arm64 cmd/evening_detective/main.go"
ls ../evening_detective_stories | grep Дело | xargs -I {} cp -f bin/evening_detective_linux_arm64 "../evening_detective_stories/{}/game/"
ls ../evening_detective_stories | grep Дело | xargs -I {} cp -f docker-compose.yml "../evening_detective_stories/{}/game/"
ls ../evening_detective_stories | grep Дело | xargs -I {} cp -f Dockerfile "../evening_detective_stories/{}/game/"
text_to_story: text_to_story:
rm -f ./cmd/text_to_story/story.json rm -f ./cmd/text_to_story/story.json
+10 -1
View File
@@ -230,9 +230,18 @@ message GraphNode {
string code = 1; string code = 1;
string name = 2; string name = 2;
string text = 3; string text = 3;
repeated GraphApplication applications = 4; string image = 4;
repeated GraphApplication applications = 5;
bool hidden = 7;
repeated GraphDoor doors = 8;
} }
message GraphApplication { message GraphApplication {
string name = 1; string name = 1;
} }
message GraphDoor {
string code = 1;
string name = 2;
bool show = 3;
}
+27 -29
View File
@@ -34,11 +34,10 @@ var userFS embed.FS
var adminFS embed.FS var adminFS embed.FS
func main() { func main() {
// Create a listener on TCP port grpcGatewayHost := config.GetGrpcGatewayHost()
lis, err := net.Listen("tcp", ":8080") userClientHost := config.GetUserClientHost()
if err != nil { adminClientHost := config.GetAdminClientHost()
log.Fatalln("Failed to listen:", err) fileHost := config.GetFileHost()
}
// Create a gRPC server object // Create a gRPC server object
s := grpc.NewServer() s := grpc.NewServer()
@@ -50,10 +49,6 @@ func main() {
log.Fatalln(err) log.Fatalln(err)
} }
clientHost := config.GetHost()
adminClientHost := config.GetAdminHost()
fileHost := config.GetFileHost()
cleaner := cleaner.NewCleaner() cleaner := cleaner.NewCleaner()
formatter := formatter.NewFormatter() formatter := formatter.NewFormatter()
@@ -72,7 +67,7 @@ func main() {
log.Fatalln(err) log.Fatalln(err)
} }
linkService := link.NewLinkService(clientHost) linkService := link.NewLinkService(userClientHost)
passwordGenerator := password.NewPasswordGenerator() passwordGenerator := password.NewPasswordGenerator()
@@ -90,12 +85,17 @@ func main() {
), ),
), ),
) )
// Serve gRPC server
log.Println("Serving gRPC on 0.0.0.0:8080") // Server gRPC
lis, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatalln("Failed to listen:", err)
}
go func() { go func() {
log.Fatalln(s.Serve(lis)) log.Fatalln(s.Serve(lis))
}() }()
// Client gRPC
// Create a client connection to the gRPC server we just started // Create a client connection to the gRPC server we just started
// This is where the gRPC-Gateway proxies the requests // This is where the gRPC-Gateway proxies the requests
conn, err := grpc.NewClient( conn, err := grpc.NewClient(
@@ -122,13 +122,12 @@ func main() {
log.Fatalln("Failed to register gateway:", err) log.Fatalln("Failed to register gateway:", err)
} }
// Server gRPC-Gateway
gwServer := &http.Server{ gwServer := &http.Server{
Addr: ":8090", Addr: config.GrpcGatewayPort,
Handler: cors(gwmux), Handler: cors(gwmux),
} }
log.Printf("Serving %s for gRPC-Gateway\n", grpcGatewayHost)
// Serve gRPC-Gateway server
log.Println("Serving gRPC-Gateway on http://0.0.0.0:8090")
go func() { go func() {
log.Fatalln(gwServer.ListenAndServe()) log.Fatalln(gwServer.ListenAndServe())
}() }()
@@ -141,23 +140,18 @@ func main() {
fileServerUser := http.FileServer(http.FS(subUserFS)) fileServerUser := http.FileServer(http.FS(subUserFS))
muxUser.Handle("/", fileServerUser) muxUser.Handle("/", fileServerUser)
// Serve user web server // Server user web
log.Println("Serving user web on http://0.0.0.0" + config.ClientPort) log.Printf("Serving %s for user web\n", userClientHost)
go func() { go func() {
log.Fatalln(http.ListenAndServe(config.ClientPort, muxUser)) log.Fatalln(http.ListenAndServe(config.UserClientPort, muxUser))
}() }()
go func() { go func() {
dir := "./data/story/images" dir := "./data/story/images"
// Создаем файловый сервер
fs := http.FileServer(http.Dir(dir)) fs := http.FileServer(http.Dir(dir))
// Добавляем middleware для логирования
http.Handle("/", loggingMiddleware(fs)) http.Handle("/", loggingMiddleware(fs))
log.Printf("Serving %s for file server, directory: %s\n", fileHost, dir)
log.Println("Файловый сервер запущен на http://localhost:8120") log.Fatal(http.ListenAndServe(config.FilePort, nil))
log.Println("Обслуживается директория: " + dir)
log.Fatal(http.ListenAndServe(":8120", nil))
}() }()
muxAdmin := http.NewServeMux() muxAdmin := http.NewServeMux()
@@ -168,9 +162,13 @@ func main() {
fileServerAdmin := http.FileServer(http.FS(subAdminFS)) fileServerAdmin := http.FileServer(http.FS(subAdminFS))
muxAdmin.Handle("/", fileServerAdmin) muxAdmin.Handle("/", fileServerAdmin)
// Serve admin web server // Server admin web
log.Printf("Serving admin web on %s\n", adminClientHost) adminWebServer := &http.Server{
log.Fatalln(http.ListenAndServe(":8110", muxAdmin)) Addr: config.AdminClientPort,
Handler: muxAdmin,
}
log.Printf("Serving %s for admin web \n", adminClientHost)
log.Fatalln(adminWebServer.ListenAndServe())
} }
func cors(h http.Handler) http.Handler { func cors(h http.Handler) http.Handler {
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
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-CH9kKe_e.js"></script> <script type="module" crossorigin src="/assets/index-c8po_p3Q.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C16dKKOO.css"> <link rel="stylesheet" crossorigin href="/assets/index-CgpxTv-m.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 MiB

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: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 859 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 603 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 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-CBvKsrC1.js"></script> <script type="module" crossorigin src="/assets/index-CQcj9qbi.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BkPf0Nib.css"> <link rel="stylesheet" crossorigin href="/assets/index-C2dfznw-.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
+42 -9
View File
@@ -6,14 +6,47 @@ services:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
ports: ports:
- "8080:8080" - "8090:8090" # api
- "8090:8090" - "8100:8100" # user
- "8100:8100" - "8110:8110" # admin
- "8110:8110" - "8120:8120" # files
environment:
- HOST=https://evening-detective.crabs-games.art
- FILE_HOST=https://evening-detective-files.crabs-games.art
networks:
- crabs-network
volumes: volumes:
- data:/data - ./data:/data
# environment: labels:
# - ENV_VAR_NAME=env_var_value # api
reproxy.1.server: "evening-detective-api.crabs-games.art"
reproxy.1.route: "/(.*)"
reproxy.1.dest: "http://evening_detective:8090/$$1"
reproxy.1.port: "8090"
reproxy.1.ping: "/"
volumes: # user
data: reproxy.2.server: "evening-detective.crabs-games.art"
reproxy.2.route: "/(.*)"
reproxy.2.dest: "http://evening_detective:8100/$$1"
reproxy.2.port: "8100"
reproxy.2.ping: "/"
# admin
reproxy.3.server: "evening-detective-admin.crabs-games.art"
reproxy.3.route: "/(.*)"
reproxy.3.dest: "http://evening_detective:8110/$$1"
reproxy.3.port: "8110"
reproxy.3.ping: "/"
# files
reproxy.4.server: "evening-detective-files.crabs-games.art"
reproxy.4.route: "/(.*)"
reproxy.4.dest: "http://evening_detective:8120/$$1"
reproxy.4.port: "8120"
reproxy.4.ping: "/"
networks:
crabs-network:
name: crabs-network
external: true
+4 -14
View File
@@ -1,24 +1,16 @@
module evening_detective module evening_detective
go 1.23.0 go 1.26
toolchain go1.24.5
require ( require (
github.com/golang/mock v1.6.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2
google.golang.org/grpc v1.75.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 (
@@ -37,9 +29,7 @@ require (
golang.org/x/net v0.41.0 // indirect golang.org/x/net v0.41.0 // indirect
golang.org/x/sys v0.33.0 // indirect golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.28.0 // indirect golang.org/x/text v0.28.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
google.golang.org/protobuf v1.36.7 // indirect google.golang.org/protobuf v1.36.7
) )
tool github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway
+18 -5
View File
@@ -7,7 +7,8 @@ import (
) )
const ( const (
ClientPort = ":8100" GrpcGatewayPort = ":8090"
UserClientPort = ":8100"
AdminClientPort = ":8110" AdminClientPort = ":8110"
FilePort = ":8120" FilePort = ":8120"
) )
@@ -20,7 +21,19 @@ func GetDBFilepath() string {
return getFilepath("DB_FILENAME", "data/db/store.db") return getFilepath("DB_FILENAME", "data/db/store.db")
} }
func GetAdminHost() string { func GetGrpcGatewayHost() string {
host := os.Getenv("HOST")
if host != "" {
return host
}
ips, err := getLocalIPs()
if err != nil || len(ips) == 0 {
return "http://127.0.0.1" + GrpcGatewayPort
}
return "http://" + ips[0] + GrpcGatewayPort
}
func GetAdminClientHost() string {
host := os.Getenv("HOST") host := os.Getenv("HOST")
if host != "" { if host != "" {
return host return host
@@ -32,16 +45,16 @@ func GetAdminHost() string {
return "http://" + ips[0] + AdminClientPort return "http://" + ips[0] + AdminClientPort
} }
func GetHost() string { func GetUserClientHost() string {
host := os.Getenv("HOST") host := os.Getenv("HOST")
if host != "" { if host != "" {
return host return host
} }
ips, err := getLocalIPs() ips, err := getLocalIPs()
if err != nil || len(ips) == 0 { if err != nil || len(ips) == 0 {
return "http://127.0.0.1" + ClientPort return "http://127.0.0.1" + UserClientPort
} }
return "http://" + ips[0] + ClientPort return "http://" + ips[0] + UserClientPort
} }
func GetFileHost() string { func GetFileHost() string {
+1
View File
@@ -2,4 +2,5 @@ package formatter
type IFormatter interface { type IFormatter interface {
FormatText(text string) string FormatText(text string) string
FormatString(text string) string
} }
+8
View File
@@ -40,3 +40,11 @@ func (s *service) FormatText(text string) string {
} }
return res.String() return res.String()
} }
func (s *service) FormatString(text string) string {
l := strings.TrimSpace(text)
if strings.HasPrefix(l, "--") {
l = strings.Replace(l, "--", "—", 1)
}
return l
}
+1 -1
View File
@@ -19,7 +19,7 @@ func NewDBService(filepath string) (IDBService, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Printf("load db from: %s", filepath) log.Printf("Load db from: %s", filepath)
_, err = db.Exec("CREATE TABLE IF NOT EXISTS teams (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL CHECK(length(trim(name)) > 0), password TEXT);") _, err = db.Exec("CREATE TABLE IF NOT EXISTS teams (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL CHECK(length(trim(name)) > 0), password TEXT);")
if err != nil { if err != nil {
return nil, err return nil, err
+28
View File
@@ -232,11 +232,25 @@ func (s *Services) UpdateNode(ctx context.Context, req *proto.UpdateNodeReq) (*p
}, },
) )
} }
doors := make([]*story_service.GraphDoor, 0, len(req.Node.Doors))
for _, door := range req.Node.Doors {
doors = append(
doors,
&story_service.GraphDoor{
Code: door.Code,
Name: door.Name,
Show: door.Show,
},
)
}
node := &story_service.GraphNode{ node := &story_service.GraphNode{
Code: req.Node.Code, Code: req.Node.Code,
Name: req.Node.Name, Name: req.Node.Name,
Text: req.Node.Text, Text: req.Node.Text,
Image: req.Node.Image,
Applications: applications, Applications: applications,
Hidden: req.Node.Hidden,
Doors: doors,
} }
if err := s.storyService.UpdatePlace(ctx, req.Code, node); err != nil { if err := s.storyService.UpdatePlace(ctx, req.Code, node); err != nil {
return nil, err return nil, err
@@ -257,11 +271,25 @@ func (s *Services) GetGraph(ctx context.Context, req *proto.GetGraphReq) (*proto
}, },
) )
} }
doors := make([]*proto.GraphDoor, 0, len(node.Doors))
for _, door := range node.Doors {
doors = append(
doors,
&proto.GraphDoor{
Code: door.Code,
Name: door.Name,
Show: door.Show,
},
)
}
nodes = append(nodes, &proto.GraphNode{ nodes = append(nodes, &proto.GraphNode{
Code: node.Code, Code: node.Code,
Name: node.Name, Name: node.Name,
Text: node.Text, Text: node.Text,
Image: node.Image,
Applications: applications, Applications: applications,
Hidden: node.Hidden,
Doors: doors,
}) })
} }
edges := make([]*proto.GetGraphRsp_Edge, 0, len(graph.Edges)) edges := make([]*proto.GetGraphRsp_Edge, 0, len(graph.Edges))
@@ -9,7 +9,10 @@ type GraphNode struct {
Code string Code string
Name string Name string
Text string Text string
Image string
Applications []*GraphApplication Applications []*GraphApplication
Hidden bool
Doors []*GraphDoor
} }
type GraphEdge struct { type GraphEdge struct {
@@ -21,3 +24,9 @@ type GraphEdge struct {
type GraphApplication struct { type GraphApplication struct {
Name string Name string
} }
type GraphDoor struct {
Code string
Name string
Show bool
}
+48 -3
View File
@@ -175,9 +175,12 @@ func (s *StoryService) updatePlace(ctx context.Context, code string, node *Graph
if s.story.Places[i].Code == code { if s.story.Places[i].Code == code {
s.story.Places[i] = models.NewPlace( s.story.Places[i] = models.NewPlace(
node.Code, node.Code,
node.Name, s.formatter.FormatString(node.Name),
s.formatter.FormatText(node.Text), s.formatter.FormatText(node.Text),
models.WithPlaceImage(node.Image),
models.WithPlaceApplication(s.getApplications(node)...), models.WithPlaceApplication(s.getApplications(node)...),
models.WithPlaceHidden(node.Hidden),
models.WithPlaceDoors(s.getDoors(node)...),
) )
return s.Update(ctx) return s.Update(ctx)
} }
@@ -186,9 +189,12 @@ func (s *StoryService) updatePlace(ctx context.Context, code string, node *Graph
if s.story.Places[i].Code == node.Code { if s.story.Places[i].Code == node.Code {
s.story.Places[i] = models.NewPlace( s.story.Places[i] = models.NewPlace(
code, code,
node.Name, s.formatter.FormatString(node.Name),
s.formatter.FormatText(node.Text), s.formatter.FormatText(node.Text),
models.WithPlaceImage(node.Image),
models.WithPlaceApplication(s.getApplications(node)...), models.WithPlaceApplication(s.getApplications(node)...),
models.WithPlaceHidden(node.Hidden),
models.WithPlaceDoors(s.getDoors(node)...),
) )
break break
} }
@@ -202,13 +208,28 @@ func (s *StoryService) getApplications(node *GraphNode) []*models.Application {
nodeApplications = append( nodeApplications = append(
nodeApplications, nodeApplications,
&models.Application{ &models.Application{
Name: application.Name, Name: s.formatter.FormatString(application.Name),
}, },
) )
} }
return nodeApplications return nodeApplications
} }
func (s *StoryService) getDoors(node *GraphNode) []*models.Door {
nodeDoors := make([]*models.Door, 0, len(node.Doors))
for _, door := range node.Doors {
nodeDoors = append(
nodeDoors,
&models.Door{
Code: door.Code,
Name: s.formatter.FormatString(door.Name),
Show: door.Show,
},
)
}
return nodeDoors
}
func (s *StoryService) GetGraph(ctx context.Context) *Graph { func (s *StoryService) GetGraph(ctx context.Context) *Graph {
m := make(map[string]string, len(s.story.Places)) m := make(map[string]string, len(s.story.Places))
nodes := make([]*GraphNode, 0, len(s.story.Places)) nodes := make([]*GraphNode, 0, len(s.story.Places))
@@ -223,12 +244,26 @@ func (s *StoryService) GetGraph(ctx context.Context) *Graph {
}, },
) )
} }
doors := make([]*GraphDoor, 0, len(place.Doors))
for _, door := range place.Doors {
doors = append(
doors,
&GraphDoor{
Code: door.Code,
Name: door.Name,
Show: door.Show,
},
)
}
nodes = append( nodes = append(
nodes, &GraphNode{ nodes, &GraphNode{
Code: place.Code, Code: place.Code,
Name: place.Name, Name: place.Name,
Text: place.Text, Text: place.Text,
Image: place.Image,
Applications: applications, Applications: applications,
Hidden: place.Hidden,
Doors: doors,
}, },
) )
} }
@@ -259,6 +294,16 @@ func (s *StoryService) GetGraph(ctx context.Context) *Graph {
) )
} }
} }
for _, door := range place.Doors {
edges = append(
edges,
&GraphEdge{
From: m[s.cleaner.ClearCode(place.Code)],
To: m[s.cleaner.ClearCode(door.Code)],
Type: "door",
},
)
}
} }
return &Graph{ return &Graph{
@@ -25,11 +25,12 @@ func (s *fileService) Load(ctx context.Context) (*models.Story, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("story file %s not found", s.filepath) return nil, fmt.Errorf("story file %s not found", s.filepath)
} }
log.Printf("load story from: %s", s.filepath) log.Printf("Load story from: %s", s.filepath)
story := &models.Story{} story := &models.Story{}
if err := json.Unmarshal(data, story); err != nil { if err := json.Unmarshal(data, story); err != nil {
return nil, err return nil, err
} }
log.Printf("Found %d places", len(story.Places))
return story, nil return story, nil
} }
+135 -42
View File
@@ -1539,7 +1539,10 @@ type GraphNode struct {
Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Text string `protobuf:"bytes,3,opt,name=text,proto3" json:"text,omitempty"` Text string `protobuf:"bytes,3,opt,name=text,proto3" json:"text,omitempty"`
Applications []*GraphApplication `protobuf:"bytes,4,rep,name=applications,proto3" json:"applications,omitempty"` Image string `protobuf:"bytes,4,opt,name=image,proto3" json:"image,omitempty"`
Applications []*GraphApplication `protobuf:"bytes,5,rep,name=applications,proto3" json:"applications,omitempty"`
Hidden bool `protobuf:"varint,7,opt,name=hidden,proto3" json:"hidden,omitempty"`
Doors []*GraphDoor `protobuf:"bytes,8,rep,name=doors,proto3" json:"doors,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -1595,6 +1598,13 @@ func (x *GraphNode) GetText() string {
return "" return ""
} }
func (x *GraphNode) GetImage() string {
if x != nil {
return x.Image
}
return ""
}
func (x *GraphNode) GetApplications() []*GraphApplication { func (x *GraphNode) GetApplications() []*GraphApplication {
if x != nil { if x != nil {
return x.Applications return x.Applications
@@ -1602,6 +1612,20 @@ func (x *GraphNode) GetApplications() []*GraphApplication {
return nil return nil
} }
func (x *GraphNode) GetHidden() bool {
if x != nil {
return x.Hidden
}
return false
}
func (x *GraphNode) GetDoors() []*GraphDoor {
if x != nil {
return x.Doors
}
return nil
}
type GraphApplication struct { type GraphApplication struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
@@ -1646,6 +1670,66 @@ func (x *GraphApplication) GetName() string {
return "" return ""
} }
type GraphDoor struct {
state protoimpl.MessageState `protogen:"open.v1"`
Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Show bool `protobuf:"varint,3,opt,name=show,proto3" json:"show,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GraphDoor) Reset() {
*x = GraphDoor{}
mi := &file_main_proto_msgTypes[34]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GraphDoor) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GraphDoor) ProtoMessage() {}
func (x *GraphDoor) ProtoReflect() protoreflect.Message {
mi := &file_main_proto_msgTypes[34]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GraphDoor.ProtoReflect.Descriptor instead.
func (*GraphDoor) Descriptor() ([]byte, []int) {
return file_main_proto_rawDescGZIP(), []int{34}
}
func (x *GraphDoor) GetCode() string {
if x != nil {
return x.Code
}
return ""
}
func (x *GraphDoor) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *GraphDoor) GetShow() bool {
if x != nil {
return x.Show
}
return false
}
type GetGraphRsp_Edge struct { type GetGraphRsp_Edge struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
From string `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` From string `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"`
@@ -1658,7 +1742,7 @@ type GetGraphRsp_Edge struct {
func (x *GetGraphRsp_Edge) Reset() { func (x *GetGraphRsp_Edge) Reset() {
*x = GetGraphRsp_Edge{} *x = GetGraphRsp_Edge{}
mi := &file_main_proto_msgTypes[34] mi := &file_main_proto_msgTypes[35]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -1670,7 +1754,7 @@ func (x *GetGraphRsp_Edge) String() string {
func (*GetGraphRsp_Edge) ProtoMessage() {} func (*GetGraphRsp_Edge) ProtoMessage() {}
func (x *GetGraphRsp_Edge) ProtoReflect() protoreflect.Message { func (x *GetGraphRsp_Edge) ProtoReflect() protoreflect.Message {
mi := &file_main_proto_msgTypes[34] mi := &file_main_proto_msgTypes[35]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -1809,14 +1893,21 @@ const file_main_proto_rawDesc = "" +
"\rUpdateNodeReq\x12\x12\n" + "\rUpdateNodeReq\x12\x12\n" +
"\x04code\x18\x01 \x01(\tR\x04code\x126\n" + "\x04code\x18\x01 \x01(\tR\x04code\x126\n" +
"\x04node\x18\x02 \x01(\v2\".crabs.evening_detective.GraphNodeR\x04node\"\x0f\n" + "\x04node\x18\x02 \x01(\v2\".crabs.evening_detective.GraphNodeR\x04node\"\x0f\n" +
"\rUpdateNodeRsp\"\x96\x01\n" + "\rUpdateNodeRsp\"\xfe\x01\n" +
"\tGraphNode\x12\x12\n" + "\tGraphNode\x12\x12\n" +
"\x04code\x18\x01 \x01(\tR\x04code\x12\x12\n" + "\x04code\x18\x01 \x01(\tR\x04code\x12\x12\n" +
"\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" +
"\x04text\x18\x03 \x01(\tR\x04text\x12M\n" + "\x04text\x18\x03 \x01(\tR\x04text\x12\x14\n" +
"\fapplications\x18\x04 \x03(\v2).crabs.evening_detective.GraphApplicationR\fapplications\"&\n" + "\x05image\x18\x04 \x01(\tR\x05image\x12M\n" +
"\fapplications\x18\x05 \x03(\v2).crabs.evening_detective.GraphApplicationR\fapplications\x12\x16\n" +
"\x06hidden\x18\a \x01(\bR\x06hidden\x128\n" +
"\x05doors\x18\b \x03(\v2\".crabs.evening_detective.GraphDoorR\x05doors\"&\n" +
"\x10GraphApplication\x12\x12\n" + "\x10GraphApplication\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name2\xe4\v\n" + "\x04name\x18\x01 \x01(\tR\x04name\"G\n" +
"\tGraphDoor\x12\x12\n" +
"\x04code\x18\x01 \x01(\tR\x04code\x12\x12\n" +
"\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" +
"\x04show\x18\x03 \x01(\bR\x04show2\xe4\v\n" +
"\x10EveningDetective\x12Y\n" + "\x10EveningDetective\x12Y\n" +
"\x04Ping\x12 .crabs.evening_detective.PingReq\x1a .crabs.evening_detective.PingRsp\"\r\x82\xd3\xe4\x93\x02\a\x12\x05/ping\x12i\n" + "\x04Ping\x12 .crabs.evening_detective.PingReq\x1a .crabs.evening_detective.PingRsp\"\r\x82\xd3\xe4\x93\x02\a\x12\x05/ping\x12i\n" +
"\bAddTeams\x12$.crabs.evening_detective.AddTeamsReq\x1a$.crabs.evening_detective.AddTeamsRsp\"\x11\x82\xd3\xe4\x93\x02\v:\x01*\"\x06/teams\x12f\n" + "\bAddTeams\x12$.crabs.evening_detective.AddTeamsReq\x1a$.crabs.evening_detective.AddTeamsRsp\"\x11\x82\xd3\xe4\x93\x02\v:\x01*\"\x06/teams\x12f\n" +
@@ -1847,7 +1938,7 @@ func file_main_proto_rawDescGZIP() []byte {
return file_main_proto_rawDescData return file_main_proto_rawDescData
} }
var file_main_proto_msgTypes = make([]protoimpl.MessageInfo, 35) var file_main_proto_msgTypes = make([]protoimpl.MessageInfo, 36)
var file_main_proto_goTypes = []any{ var file_main_proto_goTypes = []any{
(*PingReq)(nil), // 0: crabs.evening_detective.PingReq (*PingReq)(nil), // 0: crabs.evening_detective.PingReq
(*PingRsp)(nil), // 1: crabs.evening_detective.PingRsp (*PingRsp)(nil), // 1: crabs.evening_detective.PingRsp
@@ -1883,7 +1974,8 @@ var file_main_proto_goTypes = []any{
(*UpdateNodeRsp)(nil), // 31: crabs.evening_detective.UpdateNodeRsp (*UpdateNodeRsp)(nil), // 31: crabs.evening_detective.UpdateNodeRsp
(*GraphNode)(nil), // 32: crabs.evening_detective.GraphNode (*GraphNode)(nil), // 32: crabs.evening_detective.GraphNode
(*GraphApplication)(nil), // 33: crabs.evening_detective.GraphApplication (*GraphApplication)(nil), // 33: crabs.evening_detective.GraphApplication
(*GetGraphRsp_Edge)(nil), // 34: crabs.evening_detective.GetGraphRsp.Edge (*GraphDoor)(nil), // 34: crabs.evening_detective.GraphDoor
(*GetGraphRsp_Edge)(nil), // 35: crabs.evening_detective.GetGraphRsp.Edge
} }
var file_main_proto_depIdxs = []int32{ var file_main_proto_depIdxs = []int32{
3, // 0: crabs.evening_detective.AddTeamsReq.teams:type_name -> crabs.evening_detective.Team 3, // 0: crabs.evening_detective.AddTeamsReq.teams:type_name -> crabs.evening_detective.Team
@@ -1895,40 +1987,41 @@ var file_main_proto_depIdxs = []int32{
12, // 6: crabs.evening_detective.Action.doors:type_name -> crabs.evening_detective.Door 12, // 6: crabs.evening_detective.Action.doors:type_name -> crabs.evening_detective.Door
11, // 7: crabs.evening_detective.GiveApplicationsReq.applications:type_name -> crabs.evening_detective.Application 11, // 7: crabs.evening_detective.GiveApplicationsReq.applications:type_name -> crabs.evening_detective.Application
32, // 8: crabs.evening_detective.GetGraphRsp.nodes:type_name -> crabs.evening_detective.GraphNode 32, // 8: crabs.evening_detective.GetGraphRsp.nodes:type_name -> crabs.evening_detective.GraphNode
34, // 9: crabs.evening_detective.GetGraphRsp.edges:type_name -> crabs.evening_detective.GetGraphRsp.Edge 35, // 9: crabs.evening_detective.GetGraphRsp.edges:type_name -> crabs.evening_detective.GetGraphRsp.Edge
32, // 10: crabs.evening_detective.UpdateNodeReq.node:type_name -> crabs.evening_detective.GraphNode 32, // 10: crabs.evening_detective.UpdateNodeReq.node:type_name -> crabs.evening_detective.GraphNode
33, // 11: crabs.evening_detective.GraphNode.applications:type_name -> crabs.evening_detective.GraphApplication 33, // 11: crabs.evening_detective.GraphNode.applications:type_name -> crabs.evening_detective.GraphApplication
0, // 12: crabs.evening_detective.EveningDetective.Ping:input_type -> crabs.evening_detective.PingReq 34, // 12: crabs.evening_detective.GraphNode.doors:type_name -> crabs.evening_detective.GraphDoor
2, // 13: crabs.evening_detective.EveningDetective.AddTeams:input_type -> crabs.evening_detective.AddTeamsReq 0, // 13: crabs.evening_detective.EveningDetective.Ping:input_type -> crabs.evening_detective.PingReq
6, // 14: crabs.evening_detective.EveningDetective.GetTeams:input_type -> crabs.evening_detective.GetTeamsReq 2, // 14: crabs.evening_detective.EveningDetective.AddTeams:input_type -> crabs.evening_detective.AddTeamsReq
8, // 15: crabs.evening_detective.EveningDetective.GetTeamsCSV:input_type -> crabs.evening_detective.GetTeamsCSVReq 6, // 15: crabs.evening_detective.EveningDetective.GetTeams:input_type -> crabs.evening_detective.GetTeamsReq
13, // 16: crabs.evening_detective.EveningDetective.GetTeam:input_type -> crabs.evening_detective.GetTeamReq 8, // 16: crabs.evening_detective.EveningDetective.GetTeamsCSV:input_type -> crabs.evening_detective.GetTeamsCSVReq
15, // 17: crabs.evening_detective.EveningDetective.AddAction:input_type -> crabs.evening_detective.AddActionReq 13, // 17: crabs.evening_detective.EveningDetective.GetTeam:input_type -> crabs.evening_detective.GetTeamReq
18, // 18: crabs.evening_detective.EveningDetective.GetGame:input_type -> crabs.evening_detective.GetGameReq 15, // 18: crabs.evening_detective.EveningDetective.AddAction:input_type -> crabs.evening_detective.AddActionReq
20, // 19: crabs.evening_detective.EveningDetective.GameStart:input_type -> crabs.evening_detective.GameStartReq 18, // 19: crabs.evening_detective.EveningDetective.GetGame:input_type -> crabs.evening_detective.GetGameReq
22, // 20: crabs.evening_detective.EveningDetective.GameStop:input_type -> crabs.evening_detective.GameStopReq 20, // 20: crabs.evening_detective.EveningDetective.GameStart:input_type -> crabs.evening_detective.GameStartReq
24, // 21: crabs.evening_detective.EveningDetective.GiveApplications:input_type -> crabs.evening_detective.GiveApplicationsReq 22, // 21: crabs.evening_detective.EveningDetective.GameStop:input_type -> crabs.evening_detective.GameStopReq
26, // 22: crabs.evening_detective.EveningDetective.DownloadTeamsQrCodesFile:input_type -> crabs.evening_detective.DownloadTeamsQrCodesFileReq 24, // 22: crabs.evening_detective.EveningDetective.GiveApplications:input_type -> crabs.evening_detective.GiveApplicationsReq
28, // 23: crabs.evening_detective.EveningDetective.GetGraph:input_type -> crabs.evening_detective.GetGraphReq 26, // 23: crabs.evening_detective.EveningDetective.DownloadTeamsQrCodesFile:input_type -> crabs.evening_detective.DownloadTeamsQrCodesFileReq
30, // 24: crabs.evening_detective.EveningDetective.UpdateNode:input_type -> crabs.evening_detective.UpdateNodeReq 28, // 24: crabs.evening_detective.EveningDetective.GetGraph:input_type -> crabs.evening_detective.GetGraphReq
1, // 25: crabs.evening_detective.EveningDetective.Ping:output_type -> crabs.evening_detective.PingRsp 30, // 25: crabs.evening_detective.EveningDetective.UpdateNode:input_type -> crabs.evening_detective.UpdateNodeReq
4, // 26: crabs.evening_detective.EveningDetective.AddTeams:output_type -> crabs.evening_detective.AddTeamsRsp 1, // 26: crabs.evening_detective.EveningDetective.Ping:output_type -> crabs.evening_detective.PingRsp
7, // 27: crabs.evening_detective.EveningDetective.GetTeams:output_type -> crabs.evening_detective.GetTeamsRsp 4, // 27: crabs.evening_detective.EveningDetective.AddTeams:output_type -> crabs.evening_detective.AddTeamsRsp
9, // 28: crabs.evening_detective.EveningDetective.GetTeamsCSV:output_type -> crabs.evening_detective.GetTeamsCSVRsp 7, // 28: crabs.evening_detective.EveningDetective.GetTeams:output_type -> crabs.evening_detective.GetTeamsRsp
14, // 29: crabs.evening_detective.EveningDetective.GetTeam:output_type -> crabs.evening_detective.GetTeamRsp 9, // 29: crabs.evening_detective.EveningDetective.GetTeamsCSV:output_type -> crabs.evening_detective.GetTeamsCSVRsp
16, // 30: crabs.evening_detective.EveningDetective.AddAction:output_type -> crabs.evening_detective.AddActionRsp 14, // 30: crabs.evening_detective.EveningDetective.GetTeam:output_type -> crabs.evening_detective.GetTeamRsp
19, // 31: crabs.evening_detective.EveningDetective.GetGame:output_type -> crabs.evening_detective.GetGameRsp 16, // 31: crabs.evening_detective.EveningDetective.AddAction:output_type -> crabs.evening_detective.AddActionRsp
21, // 32: crabs.evening_detective.EveningDetective.GameStart:output_type -> crabs.evening_detective.GameStartRsp 19, // 32: crabs.evening_detective.EveningDetective.GetGame:output_type -> crabs.evening_detective.GetGameRsp
23, // 33: crabs.evening_detective.EveningDetective.GameStop:output_type -> crabs.evening_detective.GameStopRsp 21, // 33: crabs.evening_detective.EveningDetective.GameStart:output_type -> crabs.evening_detective.GameStartRsp
25, // 34: crabs.evening_detective.EveningDetective.GiveApplications:output_type -> crabs.evening_detective.GiveApplicationsRsp 23, // 34: crabs.evening_detective.EveningDetective.GameStop:output_type -> crabs.evening_detective.GameStopRsp
27, // 35: crabs.evening_detective.EveningDetective.DownloadTeamsQrCodesFile:output_type -> crabs.evening_detective.DownloadTeamsQrCodesFileRsp 25, // 35: crabs.evening_detective.EveningDetective.GiveApplications:output_type -> crabs.evening_detective.GiveApplicationsRsp
29, // 36: crabs.evening_detective.EveningDetective.GetGraph:output_type -> crabs.evening_detective.GetGraphRsp 27, // 36: crabs.evening_detective.EveningDetective.DownloadTeamsQrCodesFile:output_type -> crabs.evening_detective.DownloadTeamsQrCodesFileRsp
31, // 37: crabs.evening_detective.EveningDetective.UpdateNode:output_type -> crabs.evening_detective.UpdateNodeRsp 29, // 37: crabs.evening_detective.EveningDetective.GetGraph:output_type -> crabs.evening_detective.GetGraphRsp
25, // [25:38] is the sub-list for method output_type 31, // 38: crabs.evening_detective.EveningDetective.UpdateNode:output_type -> crabs.evening_detective.UpdateNodeRsp
12, // [12:25] is the sub-list for method input_type 26, // [26:39] is the sub-list for method output_type
12, // [12:12] is the sub-list for extension type_name 13, // [13:26] is the sub-list for method input_type
12, // [12:12] is the sub-list for extension extendee 13, // [13:13] is the sub-list for extension type_name
0, // [0:12] is the sub-list for field type_name 13, // [13:13] is the sub-list for extension extendee
0, // [0:13] is the sub-list for field type_name
} }
func init() { file_main_proto_init() } func init() { file_main_proto_init() }
@@ -1942,7 +2035,7 @@ func file_main_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_main_proto_rawDesc), len(file_main_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_main_proto_rawDesc), len(file_main_proto_rawDesc)),
NumEnums: 0, NumEnums: 0,
NumMessages: 35, NumMessages: 36,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },
+26
View File
@@ -607,6 +607,20 @@
} }
} }
}, },
"evening_detectiveGraphDoor": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"name": {
"type": "string"
},
"show": {
"type": "boolean"
}
}
},
"evening_detectiveGraphNode": { "evening_detectiveGraphNode": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -619,11 +633,23 @@
"text": { "text": {
"type": "string" "type": "string"
}, },
"image": {
"type": "string"
},
"applications": { "applications": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/evening_detectiveGraphApplication" "$ref": "#/definitions/evening_detectiveGraphApplication"
} }
},
"hidden": {
"type": "boolean"
},
"doors": {
"type": "array",
"items": {
"$ref": "#/definitions/evening_detectiveGraphDoor"
}
} }
} }
}, },