This commit is contained in:
2026-03-07 03:57:45 +07:00
parent c3e5654ab4
commit 9e0a19d25a
10 changed files with 6 additions and 6 deletions
+28
View File
@@ -0,0 +1,28 @@
//go:generate mockgen -source=interface.go -destination=mocks/mock.go -package=mocks
package db
import (
"context"
"evening_detective/internal/models"
)
type IDBService interface {
Close()
GetTeams(ctx context.Context) ([]*models.Team, error)
GetTeam(ctx context.Context, teamId any, password any) (*models.Team, error)
AddTeams(ctx context.Context, teams []*models.Team) ([]*models.Team, error)
DeleteAllTeams(ctx context.Context) error
GetActions(ctx context.Context, teamId int64) ([]*models.Action, error)
AddActions(ctx context.Context, teamId int64, actions []*models.Action) error
GetApplications(ctx context.Context, teamId int64) ([]*models.Application, error)
GetApplicationsByState(ctx context.Context, teamId int64, state string) ([]*models.Application, error)
AddApplications(ctx context.Context, teamId int64, applications []*models.Application) error
GiveApplications(ctx context.Context, teamId int64, applications []*models.Application) error
GetGame(ctx context.Context) (*models.Game, error)
UpdateGameState(ctx context.Context, state string) error
}
+223
View File
@@ -0,0 +1,223 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: interface.go
// Package mocks is a generated GoMock package.
package mocks
import (
context "context"
models "evening_detective/internal/models"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockIDBService is a mock of IDBService interface.
type MockIDBService struct {
ctrl *gomock.Controller
recorder *MockIDBServiceMockRecorder
}
// MockIDBServiceMockRecorder is the mock recorder for MockIDBService.
type MockIDBServiceMockRecorder struct {
mock *MockIDBService
}
// NewMockIDBService creates a new mock instance.
func NewMockIDBService(ctrl *gomock.Controller) *MockIDBService {
mock := &MockIDBService{ctrl: ctrl}
mock.recorder = &MockIDBServiceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockIDBService) EXPECT() *MockIDBServiceMockRecorder {
return m.recorder
}
// AddActions mocks base method.
func (m *MockIDBService) AddActions(ctx context.Context, teamId int64, actions []*models.Action) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddActions", ctx, teamId, actions)
ret0, _ := ret[0].(error)
return ret0
}
// AddActions indicates an expected call of AddActions.
func (mr *MockIDBServiceMockRecorder) AddActions(ctx, teamId, actions interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddActions", reflect.TypeOf((*MockIDBService)(nil).AddActions), ctx, teamId, actions)
}
// AddApplications mocks base method.
func (m *MockIDBService) AddApplications(ctx context.Context, teamId int64, applications []*models.Application) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddApplications", ctx, teamId, applications)
ret0, _ := ret[0].(error)
return ret0
}
// AddApplications indicates an expected call of AddApplications.
func (mr *MockIDBServiceMockRecorder) AddApplications(ctx, teamId, applications interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddApplications", reflect.TypeOf((*MockIDBService)(nil).AddApplications), ctx, teamId, applications)
}
// AddTeams mocks base method.
func (m *MockIDBService) AddTeams(ctx context.Context, teams []*models.Team) ([]*models.Team, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddTeams", ctx, teams)
ret0, _ := ret[0].([]*models.Team)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AddTeams indicates an expected call of AddTeams.
func (mr *MockIDBServiceMockRecorder) AddTeams(ctx, teams interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddTeams", reflect.TypeOf((*MockIDBService)(nil).AddTeams), ctx, teams)
}
// Close mocks base method.
func (m *MockIDBService) Close() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Close")
}
// Close indicates an expected call of Close.
func (mr *MockIDBServiceMockRecorder) Close() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockIDBService)(nil).Close))
}
// DeleteAllTeams mocks base method.
func (m *MockIDBService) DeleteAllTeams(ctx context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteAllTeams", ctx)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteAllTeams indicates an expected call of DeleteAllTeams.
func (mr *MockIDBServiceMockRecorder) DeleteAllTeams(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllTeams", reflect.TypeOf((*MockIDBService)(nil).DeleteAllTeams), ctx)
}
// GetActions mocks base method.
func (m *MockIDBService) GetActions(ctx context.Context, teamId int64) ([]*models.Action, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetActions", ctx, teamId)
ret0, _ := ret[0].([]*models.Action)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetActions indicates an expected call of GetActions.
func (mr *MockIDBServiceMockRecorder) GetActions(ctx, teamId interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActions", reflect.TypeOf((*MockIDBService)(nil).GetActions), ctx, teamId)
}
// GetApplications mocks base method.
func (m *MockIDBService) GetApplications(ctx context.Context, teamId int64) ([]*models.Application, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetApplications", ctx, teamId)
ret0, _ := ret[0].([]*models.Application)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetApplications indicates an expected call of GetApplications.
func (mr *MockIDBServiceMockRecorder) GetApplications(ctx, teamId interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetApplications", reflect.TypeOf((*MockIDBService)(nil).GetApplications), ctx, teamId)
}
// GetApplicationsByState mocks base method.
func (m *MockIDBService) GetApplicationsByState(ctx context.Context, teamId int64, state string) ([]*models.Application, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetApplicationsByState", ctx, teamId, state)
ret0, _ := ret[0].([]*models.Application)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetApplicationsByState indicates an expected call of GetApplicationsByState.
func (mr *MockIDBServiceMockRecorder) GetApplicationsByState(ctx, teamId, state interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetApplicationsByState", reflect.TypeOf((*MockIDBService)(nil).GetApplicationsByState), ctx, teamId, state)
}
// GetGame mocks base method.
func (m *MockIDBService) GetGame(ctx context.Context) (*models.Game, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetGame", ctx)
ret0, _ := ret[0].(*models.Game)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetGame indicates an expected call of GetGame.
func (mr *MockIDBServiceMockRecorder) GetGame(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetGame", reflect.TypeOf((*MockIDBService)(nil).GetGame), ctx)
}
// GetTeam mocks base method.
func (m *MockIDBService) GetTeam(ctx context.Context, teamId, password any) (*models.Team, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetTeam", ctx, teamId, password)
ret0, _ := ret[0].(*models.Team)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetTeam indicates an expected call of GetTeam.
func (mr *MockIDBServiceMockRecorder) GetTeam(ctx, teamId, password interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTeam", reflect.TypeOf((*MockIDBService)(nil).GetTeam), ctx, teamId, password)
}
// GetTeams mocks base method.
func (m *MockIDBService) GetTeams(ctx context.Context) ([]*models.Team, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetTeams", ctx)
ret0, _ := ret[0].([]*models.Team)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetTeams indicates an expected call of GetTeams.
func (mr *MockIDBServiceMockRecorder) GetTeams(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTeams", reflect.TypeOf((*MockIDBService)(nil).GetTeams), ctx)
}
// GiveApplications mocks base method.
func (m *MockIDBService) GiveApplications(ctx context.Context, teamId int64, applications []*models.Application) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GiveApplications", ctx, teamId, applications)
ret0, _ := ret[0].(error)
return ret0
}
// GiveApplications indicates an expected call of GiveApplications.
func (mr *MockIDBServiceMockRecorder) GiveApplications(ctx, teamId, applications interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GiveApplications", reflect.TypeOf((*MockIDBService)(nil).GiveApplications), ctx, teamId, applications)
}
// UpdateGameState mocks base method.
func (m *MockIDBService) UpdateGameState(ctx context.Context, state string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateGameState", ctx, state)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateGameState indicates an expected call of UpdateGameState.
func (mr *MockIDBServiceMockRecorder) UpdateGameState(ctx, state interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateGameState", reflect.TypeOf((*MockIDBService)(nil).UpdateGameState), ctx, state)
}
+236
View File
@@ -0,0 +1,236 @@
package db
import (
"context"
"database/sql"
"errors"
"evening_detective/internal/models"
"log"
_ "github.com/mattn/go-sqlite3"
)
type service struct {
db *sql.DB
}
func NewDBService(filepath string) (IDBService, error) {
db, err := sql.Open("sqlite3", filepath)
if err != nil {
return nil, err
}
log.Printf("load db from: %s", filepath)
_, err = db.Exec("CREATE TABLE IF NOT EXISTS teams (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL CHECK(length(trim(name)) > 0), password TEXT);")
if err != nil {
return nil, err
}
_, err = db.Exec("CREATE TABLE IF NOT EXISTS actions (id INTEGER PRIMARY KEY AUTOINCREMENT, place TEXT, teamId INTEGER, FOREIGN KEY (teamId) REFERENCES teams(id) ON DELETE CASCADE);")
if err != nil {
return nil, err
}
_, err = db.Exec("CREATE TABLE IF NOT EXISTS applications (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, teamId INTEGER, state TEXT, FOREIGN KEY (teamId) REFERENCES teams(id) ON DELETE CASCADE);")
if err != nil {
return nil, err
}
_, err = db.Exec("CREATE TABLE IF NOT EXISTS games (id INTEGER PRIMARY KEY AUTOINCREMENT, state TEXT, startAt TEXT, endAt TEXT);")
if err != nil {
return nil, err
}
return &service{db: db}, nil
}
func (s *service) Close() {
s.db.Close()
}
func (s *service) GetTeams(ctx context.Context) ([]*models.Team, error) {
rows, err := s.db.Query("select id, name, password from teams")
if err != nil {
return nil, err
}
defer rows.Close()
teams := []*models.Team{}
for rows.Next() {
item := &models.Team{}
err := rows.Scan(&item.ID, &item.Name, &item.Password)
if err != nil {
return nil, err
}
teams = append(teams, item)
}
return teams, nil
}
func (s *service) GetTeam(ctx context.Context, teamId any, password any) (*models.Team, error) {
rows, err := s.db.Query("select id, name from teams where LOWER(name) = LOWER($1) and password = $2", teamId, password)
if err != nil {
return nil, err
}
defer rows.Close()
teams := []*models.Team{}
for rows.Next() {
item := &models.Team{}
err := rows.Scan(&item.ID, &item.Name)
if err != nil {
return nil, err
}
teams = append(teams, item)
}
if len(teams) != 1 {
return nil, errors.New("bad result")
}
return teams[0], nil
}
func (s *service) AddTeams(ctx context.Context, teams []*models.Team) ([]*models.Team, error) {
for _, team := range teams {
result, err := s.db.Exec("insert into teams (name, password) values ($1, $2)", team.Name, team.Password)
if err != nil {
return nil, err
}
team.ID, err = result.LastInsertId()
if err != nil {
return nil, err
}
}
return teams, nil
}
func (s *service) DeleteAllTeams(ctx context.Context) error {
_, err := s.db.Exec("delete from teams where 1")
return err
}
func (s *service) GetActions(ctx context.Context, teamId int64) ([]*models.Action, error) {
rows, err := s.db.Query("select id, place from actions where teamId = $1", teamId)
if err != nil {
return nil, err
}
defer rows.Close()
actions := []*models.Action{}
for rows.Next() {
item := &models.Action{}
err := rows.Scan(&item.ID, &item.Place)
if err != nil {
return nil, err
}
actions = append(actions, item)
}
return actions, nil
}
func (s *service) AddActions(ctx context.Context, teamId int64, actions []*models.Action) error {
for _, action := range actions {
_, err := s.db.Exec("insert into actions (place, teamId) values ($1, $2)", action.Place, teamId)
if err != nil {
return err
}
}
return nil
}
func (s *service) GetApplications(ctx context.Context, teamId int64) ([]*models.Application, error) {
rows, err := s.db.Query("select id, name from applications where teamId = $1", teamId)
if err != nil {
return nil, err
}
defer rows.Close()
applications := []*models.Application{}
for rows.Next() {
item := &models.Application{}
err := rows.Scan(&item.ID, &item.Name)
if err != nil {
return nil, err
}
applications = append(applications, item)
}
return applications, nil
}
func (s *service) GetApplicationsByState(ctx context.Context, teamId int64, state string) ([]*models.Application, error) {
rows, err := s.db.Query("select id, name from applications where teamId = $1 and state = $2", teamId, state)
if err != nil {
return nil, err
}
defer rows.Close()
applications := []*models.Application{}
for rows.Next() {
item := &models.Application{}
err := rows.Scan(&item.ID, &item.Name)
if err != nil {
return nil, err
}
applications = append(applications, item)
}
return applications, nil
}
func (s *service) AddApplications(ctx context.Context, teamId int64, applications []*models.Application) error {
for _, application := range applications {
_, err := s.db.Exec("insert into applications (name, teamId, state) values ($1, $2, $3)", application.Name, teamId, application.State)
if err != nil {
return err
}
}
return nil
}
func (s *service) GiveApplications(ctx context.Context, teamId int64, applications []*models.Application) error {
for _, application := range applications {
_, err := s.db.Exec("update applications set state = \"gave\" where teamId = $1 and id = $2", teamId, application.ID)
if err != nil {
return err
}
}
return nil
}
func (s *service) GetGame(ctx context.Context) (*models.Game, error) {
rows, err := s.db.Query("select state, startAt, endAt from games limit 1")
if err != nil {
return nil, err
}
defer rows.Close()
game := &models.Game{}
if rows.Next() {
err := rows.Scan(&game.State, &game.StartTime, &game.EndTime)
if err != nil {
return nil, err
}
return game, nil
}
state := "NEW"
_, err = s.db.Exec("insert into games (state, startAt, endAt) values ($1, '', '')", state)
if err != nil {
return nil, err
}
game.State = state
return game, nil
}
func (s *service) UpdateGameState(ctx context.Context, state string) error {
game, err := s.GetGame(ctx)
if err != nil {
return err
}
switch state {
case "RUN":
if game.StartTime == "" {
_, err := s.db.Exec("update games set state = $1, startAt = datetime('now', 'localtime')", state)
return err
}
_, err := s.db.Exec("update games set state = $1", state)
return err
case "STOP":
_, err := s.db.Exec("update games set state = $1, endAt = datetime('now', 'localtime')", state)
return err
}
return nil
}
+5
View File
@@ -0,0 +1,5 @@
package link
type ILinkService interface {
GetTeamClientLink(host string, name string, password string) string
}
+16
View File
@@ -0,0 +1,16 @@
package link
import (
"fmt"
"net/url"
)
type service struct{}
func NewLinkService() ILinkService {
return &service{}
}
func (s *service) GetTeamClientLink(host string, name string, password string) string {
return fmt.Sprintf("%s?name=%s&password=%s", host, url.PathEscape(name), password)
}
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
package pdf
import "evening_detective/internal/models"
type IPDFGenerator interface {
CreateTeamsPDF(teams []*models.Team) ([]byte, error)
}
+135
View File
@@ -0,0 +1,135 @@
package pdf
import (
"bytes"
"embed"
"evening_detective/internal/models"
"strings"
"github.com/signintech/gopdf"
"github.com/skip2/go-qrcode"
)
//go:embed JetBrainsMono-Medium.ttf
var f embed.FS
type service struct{}
func NewPDFGenerator() IPDFGenerator {
return &service{}
}
func (s *service) CreateTeamsPDF(teams []*models.Team) ([]byte, error) {
pdf := &gopdf.GoPdf{}
pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4}) // W: 595, H: 842
file, err := f.Open("JetBrainsMono-Medium.ttf")
if err != nil {
return nil, err
}
if err := pdf.AddTTFFontByReader("main", file); err != nil {
return nil, err
}
if err := pdf.SetFont("main", "", 10); err != nil {
return nil, err
}
countOnPage := 9
padding := 17.
xDelta := 187.
yDelta := 260.
for i, team := range teams {
if i%countOnPage == 0 {
pdf.AddPage()
pdf.SetPage(1 + i/countOnPage)
}
y := (padding + 15) + yDelta*float64(i%countOnPage/3)
x := padding + xDelta*float64(i%3)
if err := printTextCenter(pdf, "Подключите Wi-Fi", xDelta-6, x+3, y); err != nil {
return nil, err
}
if err := printTextCenter(pdf, "Имя: evening_detective", xDelta-6, x+3, 15+y); err != nil {
return nil, err
}
if err := printTextCenter(pdf, "Пароль: 12345678", xDelta-6, x+3, 30+y); err != nil {
return nil, err
}
if err := printQR(pdf, team.Link, x+21, 65+y); err != nil {
return nil, err
}
if err := printTextCenter(pdf, "Войдите в приложение по qr", xDelta-6, x+3, 55+y); err != nil {
return nil, err
}
if err := printTextCenter(pdf, team.Name, xDelta-6, x+3, 55+y+150); err != nil {
return nil, err
}
}
buffer := &bytes.Buffer{}
_, err = pdf.WriteTo(buffer)
return buffer.Bytes(), err
}
func printQR(pdf *gopdf.GoPdf, url string, x, y float64) error {
png, err := qrcode.Encode(url, qrcode.Medium, 256)
if err != nil {
return err
}
imgHolder, err := gopdf.ImageHolderByBytes(png)
if err != nil {
return err
}
err = pdf.ImageByHolder(imgHolder, x, y, &gopdf.Rect{W: 145, H: 145})
if err != nil {
return err
}
return nil
}
func printTextCenter(pdf *gopdf.GoPdf, text string, pageWidth, x, y float64) error {
lines := splitTextByWords(pdf, text, pageWidth)
for i, line := range lines {
lineWidth, err := pdf.MeasureTextWidth(line)
if err != nil {
return err
}
xLine := x + (pageWidth-lineWidth)/2
yLine := y + 15*float64(i)
pdf.SetXY(xLine, yLine)
pdf.Cell(nil, line)
}
return nil
}
func splitTextByWords(pdf *gopdf.GoPdf, text string, maxWidth float64) []string {
words := strings.Fields(text)
var lines []string
var currentLine string
for _, word := range words {
testLine := currentLine
if testLine != "" {
testLine += " " + word
} else {
testLine = word
}
width, _ := pdf.MeasureTextWidth(testLine)
if width <= maxWidth {
currentLine = testLine
} else {
if currentLine != "" {
lines = append(lines, currentLine)
}
currentLine = word
}
}
if currentLine != "" {
lines = append(lines, currentLine)
}
return lines
}
+3 -3
View File
@@ -5,10 +5,10 @@ import (
"encoding/base64"
"encoding/json"
"evening_detective/internal/models"
"evening_detective/internal/modules/db"
"evening_detective/internal/modules/link"
"evening_detective/internal/modules/password"
"evening_detective/internal/modules/pdf"
"evening_detective/internal/services/db"
"evening_detective/internal/services/link"
"evening_detective/internal/services/pdf"
"evening_detective/internal/services/story_service"
"evening_detective/proto"
"fmt"