add images

This commit is contained in:
Владимир Фёдоров 2026-03-14 17:01:11 +07:00
parent ae82f8e0d9
commit 3a30123096
10 changed files with 65 additions and 32 deletions

View File

@ -50,6 +50,9 @@ func main() {
log.Fatalln(err)
}
clientHost := config.GetHost()
fileHost := config.GetFileHost()
cleaner := cleaner.NewCleaner()
formatter := formatter.NewFormatter()
@ -58,18 +61,22 @@ func main() {
storyStorage := story_storage.NewFileStoryStorage(storyFilepath)
storyService, err := story_service.NewStoryService(cleaner, formatter, storyStorage)
storyService, err := story_service.NewStoryService(
cleaner,
formatter,
storyStorage,
link.NewLinkService(fileHost),
)
if err != nil {
log.Fatalln(err)
}
linkService := link.NewLinkService()
linkService := link.NewLinkService(clientHost)
passwordGenerator := password.NewPasswordGenerator()
pdfGenerator := pdf.NewPDFGenerator()
clientHost := config.GetHost()
proto.RegisterEveningDetectiveServer(
s,
app.NewServer(
@ -79,7 +86,6 @@ func main() {
linkService,
passwordGenerator,
pdfGenerator,
clientHost,
),
),
)

View File

@ -1 +1 @@
: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(97, 74, 22);--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}.button-dialog{background-color:var(--main-color);font-weight:600;color:#fff;padding:6px 14px;border:1px solid #ddd;border-radius:15px;font-size:16px;margin-right:6px}.button-dialog:hover{background-color:var(--main-color);opacity:.9}.button-dialog: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-dec355e5]{overflow:hidden}.hr[data-v-dec355e5]{margin:7px 0}.body-custom[data-v-dec355e5]{font-size:medium}.info-custom[data-v-dec355e5]{padding-left:15px}.logo[data-v-dec355e5]{float:left;margin:10px}.logo-right[data-v-dec355e5]{float:right;margin:12px}.second-color[data-v-dec355e5]{color:var(--second-color)}.form-custom[data-v-dec355e5]{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-dec355e5]{border:1px solid #444444;border-radius:15px;margin:12px 10px;padding:16px;background-color:var(--main-back-item-color)}.message-header[data-v-dec355e5]{font-size:large;font-weight:200}.message-content[data-v-dec355e5]{font-weight:500;white-space:pre-wrap}.message-footer[data-v-dec355e5]{font-weight:400;color:var(--second-color)}.form-block[data-v-dec355e5]{height:140px}.messages-block[data-v-dec355e5]{height:calc(100dvh - 200px);overflow-y:auto;scrollbar-width:none}@media (min-width: 1025px){.center-block-custom[data-v-dec355e5]{width:700px;margin:0 auto}}.center-message[data-v-dec355e5]{height:calc(100dvh - 140px)}.qr[data-v-dec355e5]{text-align:center;width:200px}.collapse-icon[data-v-dec355e5]{padding:0 15px;cursor:pointer}.team-name-block[data-v-dec355e5]{float:right;padding:0 20px}.text-truncate[data-v-dec355e5]{width:100px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:2px 7px;margin:0 20px;background:#284557;border-radius:4px;font-size:medium}.error-message[data-v-13746d20]{color:brown;margin:16px 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(97, 74, 22);--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}.button-dialog{background-color:var(--main-color);font-weight:600;color:#fff;padding:6px 14px;border:1px solid #ddd;border-radius:15px;font-size:16px;margin-right:6px}.button-dialog:hover{background-color:var(--main-color);opacity:.9}.button-dialog: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-127d6c85]{overflow:hidden}.hr[data-v-127d6c85]{margin:7px 0}.body-custom[data-v-127d6c85]{font-size:medium}.info-custom[data-v-127d6c85]{padding-left:15px}.logo[data-v-127d6c85]{float:left;margin:10px}.logo-right[data-v-127d6c85]{float:right;margin:12px}.second-color[data-v-127d6c85]{color:var(--second-color)}.form-custom[data-v-127d6c85]{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-127d6c85]{border:1px solid #444444;border-radius:15px;margin:12px 10px;padding:16px;background-color:var(--main-back-item-color);display:flow-root}.message-header[data-v-127d6c85]{font-size:large;font-weight:200}.message-content[data-v-127d6c85]{font-weight:500;white-space:pre-wrap}.message-image[data-v-127d6c85]{width:40%;float:left;margin-right:15px}.message-footer[data-v-127d6c85]{font-weight:400;color:var(--second-color)}.form-block[data-v-127d6c85]{height:140px}.messages-block[data-v-127d6c85]{height:calc(100dvh - 200px);overflow-y:auto;scrollbar-width:none}@media (min-width: 1025px){.center-block-custom[data-v-127d6c85]{width:700px;margin:0 auto}}.center-message[data-v-127d6c85]{height:calc(100dvh - 140px)}.qr[data-v-127d6c85]{text-align:center;width:200px}.collapse-icon[data-v-127d6c85]{padding:0 15px;cursor:pointer}.team-name-block[data-v-127d6c85]{float:right;padding:0 20px}.text-truncate[data-v-127d6c85]{width:100px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:2px 7px;margin:0 20px;background:#284557;border-radius:4px;font-size:medium}.error-message[data-v-13746d20]{color:brown;margin:16px 0}

View File

@ -5,8 +5,8 @@
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Вечерний детектив</title>
<script type="module" crossorigin src="/assets/index-7Qo2US5x.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-2Py9RyJv.css">
<script type="module" crossorigin src="/assets/index-bGblozls.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BJOT4xui.css">
</head>
<body>
<div id="app"></div>

View File

@ -8,6 +8,7 @@ import (
const (
ClientPort = ":8100"
FilePort = ":8120"
)
func GetStoryFilepath() string {
@ -30,6 +31,18 @@ func GetHost() string {
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)

View File

@ -1,5 +1,6 @@
package link
type ILinkService interface {
GetTeamClientLink(host string, name string, password string) string
GetTeamClientLink(name string, password string) string
GetImageLink(name string) string
}

View File

@ -5,12 +5,20 @@ import (
"net/url"
)
type service struct{}
func NewLinkService() ILinkService {
return &service{}
type service struct {
host string
}
func (s *service) GetTeamClientLink(host string, name string, password string) string {
return fmt.Sprintf("%s?name=%s&password=%s", host, url.PathEscape(name), password)
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 {
return fmt.Sprintf("%s/%s", s.host, name)
}

View File

@ -25,7 +25,6 @@ type Services struct {
linkService link.ILinkService
passwordGenerator password.IPasswordGenerator
pdfGenerator pdf.IPDFGenerator
clientHost string
}
func NewServices(
@ -34,7 +33,6 @@ func NewServices(
linkService link.ILinkService,
passwordGenerator password.IPasswordGenerator,
pdfGenerator pdf.IPDFGenerator,
clientHost string,
) *Services {
return &Services{
dbService: dbService,
@ -42,7 +40,6 @@ func NewServices(
linkService: linkService,
passwordGenerator: passwordGenerator,
pdfGenerator: pdfGenerator,
clientHost: clientHost,
}
}
@ -141,8 +138,9 @@ func (s *Services) GetTeam(ctx context.Context, req *proto.GetTeamReq) (*proto.G
res := make([]*proto.Action, 0, len(actions))
for _, place := range s.storyService.GetPlaces(actionsCodes) {
newAction := mapPlaceToProtoAction(place)
newAction.Text = place.Text
newAction.Name = place.Name
newAction.Text = place.Text
newAction.Image = place.Image
newAction.Applications = make([]*proto.Application, 0, len(place.Applications))
for _, application := range place.Applications {
newAction.Applications = append(newAction.Applications, mapStoryApplicationToProtoApplication(application))
@ -176,7 +174,7 @@ func (s *Services) GetTeams(ctx context.Context, _ *proto.GetTeamsReq) (*proto.G
if err != nil {
return nil, err
}
newTeam.Url = s.linkService.GetTeamClientLink(s.clientHost, team.Name, team.Password)
newTeam.Url = s.linkService.GetTeamClientLink(team.Name, team.Password)
newTeam.SpendTime = int64(len(actions))
currentApplications, err := s.dbService.GetApplicationsByState(ctx, team.ID, "NEW")
if err != nil {
@ -214,7 +212,7 @@ func (s *Services) DownloadTeamsQrCodesFile(ctx context.Context, req *proto.Down
return nil, err
}
for _, team := range teams {
team.Link = s.linkService.GetTeamClientLink(s.clientHost, team.Name, team.Password)
team.Link = s.linkService.GetTeamClientLink(team.Name, team.Password)
}
b, err := s.pdfGenerator.CreateTeamsPDF(teams)
if err != nil {

View File

@ -4,6 +4,7 @@ import (
"context"
"evening_detective/internal/modules/cleaner"
"evening_detective/internal/modules/formatter"
"evening_detective/internal/services/link"
"evening_detective/internal/services/story_service/models"
"regexp"
"strings"
@ -14,17 +15,20 @@ type StoryService struct {
formatter formatter.IFormatter
story *models.Story
storyStorage IStoryStorage
linkService link.ILinkService
}
func NewStoryService(
cleaner cleaner.ICleaner,
formatter formatter.IFormatter,
storyStorage IStoryStorage,
linkService link.ILinkService,
) (*StoryService, error) {
s := &StoryService{
cleaner: cleaner,
formatter: formatter,
storyStorage: storyStorage,
linkService: linkService,
}
story, err := s.storyStorage.Load(context.Background())
if err != nil {
@ -77,7 +81,7 @@ func (s *StoryService) GetPlace(code string) *models.Place {
place.Code,
place.Name,
s.cleaner.ClearText(place.Text),
models.WithPlaceImage(place.Image),
models.WithPlaceImage(s.linkService.GetImageLink(place.Image)),
models.WithPlaceApplication(applications...),
models.WithPlaceHidden(place.Hidden),
models.WithPlaceDoors(doors...),

View File

@ -3,6 +3,7 @@ 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"
@ -147,6 +148,7 @@ func TestStoryService_GetPlace(t *testing.T) {
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)
@ -461,6 +463,7 @@ func TestStoryService_GetPlaces(t *testing.T) {
cleaner.NewCleaner(),
formatter.NewFormatter(),
story_storage.NewVarStoryStorage(tt.story),
link.NewLinkService("http://localhost:8120"),
)
assert.Nil(t, err)