generated from VLADIMIR/template
add local
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/evening_detective_server?sslmode=disable
|
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/evening_detective_server?sslmode=disable
|
||||||
|
|
||||||
|
# local отключает SMTP, фоновые письма и внешний webhook регистрации команд.
|
||||||
|
# Локальные учётные записи создаются отдельно; почтовый сброс пароля недоступен.
|
||||||
|
APP_MODE=online
|
||||||
|
|
||||||
# Заполните данными внешнего SMTP-релея; для локальных тестов можно оставить пустыми.
|
# Заполните данными внешнего SMTP-релея; для локальных тестов можно оставить пустыми.
|
||||||
SMTP_HOST=
|
SMTP_HOST=
|
||||||
SMTP_PORT=465
|
SMTP_PORT=465
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ const grpcMsgLimit = 257 << 20
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
_ = godotenv.Load()
|
_ = godotenv.Load()
|
||||||
|
localMode := os.Getenv("APP_MODE") == "local"
|
||||||
|
|
||||||
dbpool, err := pgxpool.New(context.Background(), os.Getenv("DATABASE_URL"))
|
dbpool, err := pgxpool.New(context.Background(), os.Getenv("DATABASE_URL"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -98,7 +99,9 @@ func main() {
|
|||||||
smtpTimeout = d
|
smtpTimeout = d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emailSender := email_sender.NewSender(
|
var emailSender email_sender.IEmailSender
|
||||||
|
if !localMode {
|
||||||
|
emailSender = email_sender.NewSender(
|
||||||
os.Getenv("SMTP_HOST"),
|
os.Getenv("SMTP_HOST"),
|
||||||
os.Getenv("SMTP_PORT"),
|
os.Getenv("SMTP_PORT"),
|
||||||
os.Getenv("SMTP_USER"),
|
os.Getenv("SMTP_USER"),
|
||||||
@@ -107,6 +110,7 @@ func main() {
|
|||||||
os.Getenv("SMTP_REPLY_TO"),
|
os.Getenv("SMTP_REPLY_TO"),
|
||||||
smtpTimeout,
|
smtpTimeout,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
processorJWT := processor_jwt.NewProcessor(os.Getenv("JWT_SECRET"))
|
processorJWT := processor_jwt.NewProcessor(os.Getenv("JWT_SECRET"))
|
||||||
refreshTokensRepo := refresh_tokens_repo.NewRefreshTokensRepo(dbpool)
|
refreshTokensRepo := refresh_tokens_repo.NewRefreshTokensRepo(dbpool)
|
||||||
usersService := users_service.NewUsersService(usersRepo, passwordGenerator, emailSender, processorJWT, refreshTokensRepo, os.Getenv("APP_BASE_URL"))
|
usersService := users_service.NewUsersService(usersRepo, passwordGenerator, emailSender, processorJWT, refreshTokensRepo, os.Getenv("APP_BASE_URL"))
|
||||||
@@ -167,8 +171,10 @@ func main() {
|
|||||||
|
|
||||||
participationService := participation_service.NewParticipationService(teamRepo, os.Getenv("APP_BASE_URL"))
|
participationService := participation_service.NewParticipationService(teamRepo, os.Getenv("APP_BASE_URL"))
|
||||||
ticketService := ticket_service.NewTicketService(ticketRepo, os.Getenv("APP_BASE_URL"))
|
ticketService := ticket_service.NewTicketService(ticketRepo, os.Getenv("APP_BASE_URL"))
|
||||||
|
if !localMode {
|
||||||
go participationService.RunParticipationEmails(context.Background(), emailSender, os.Getenv("PARTICIPATION_SECRET"), os.Getenv("GAME_TIMEZONE"))
|
go participationService.RunParticipationEmails(context.Background(), emailSender, os.Getenv("PARTICIPATION_SECRET"), os.Getenv("GAME_TIMEZONE"))
|
||||||
go reviewService.RunReviewReminderEmails(context.Background(), emailSender)
|
go reviewService.RunReviewReminderEmails(context.Background(), emailSender)
|
||||||
|
}
|
||||||
|
|
||||||
lis, err := net.Listen("tcp", ":8080")
|
lis, err := net.Listen("tcp", ":8080")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -289,7 +295,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mainMux := http.NewServeMux()
|
mainMux := http.NewServeMux()
|
||||||
|
if !localMode {
|
||||||
mainMux.Handle("POST /api/games/{id}/registrations", app.TeamRegistrationHandler(os.Getenv("YANDEX_FORMS_TOKEN"), teamService.RegisterTeam))
|
mainMux.Handle("POST /api/games/{id}/registrations", app.TeamRegistrationHandler(os.Getenv("YANDEX_FORMS_TOKEN"), teamService.RegisterTeam))
|
||||||
|
}
|
||||||
|
|
||||||
docsHandler := v5emb.New("API", "/swagger/doc.json", "/docs/")
|
docsHandler := v5emb.New("API", "/swagger/doc.json", "/docs/")
|
||||||
mainMux.Handle("/docs/", docsHandler)
|
mainMux.Handle("/docs/", docsHandler)
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"evening_detective_server/internal/services/users_service"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLocalAccountErrorStatus(t *testing.T) {
|
||||||
|
for _, test := range []struct{ name string }{{"email unavailable"}} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
err := userErrorStatus(fmt.Errorf("private detail: %w", users_service.ErrEmailUnavailable), "account failed")
|
||||||
|
if status.Code(err) != codes.FailedPrecondition || status.Convert(err).Message() != users_service.ErrEmailUnavailable.Error() {
|
||||||
|
t.Fatalf("status = %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,8 @@ import (
|
|||||||
|
|
||||||
func userErrorStatus(err error, internalMessage string) error {
|
func userErrorStatus(err error, internalMessage string) error {
|
||||||
switch {
|
switch {
|
||||||
|
case errors.Is(err, users_service.ErrEmailUnavailable):
|
||||||
|
return status.Error(codes.FailedPrecondition, users_service.ErrEmailUnavailable.Error())
|
||||||
case errors.Is(err, users_service.ErrUserNotFound):
|
case errors.Is(err, users_service.ErrUserNotFound):
|
||||||
return status.Error(codes.NotFound, err.Error())
|
return status.Error(codes.NotFound, err.Error())
|
||||||
case errors.Is(err, users_service.ErrTermsNotAccepted), errors.Is(err, users_service.ErrInvalidEmail), errors.Is(err, users_service.ErrInvalidRole), errors.Is(err, users_service.ErrInvalidPasswordChange):
|
case errors.Is(err, users_service.ErrTermsNotAccepted), errors.Is(err, users_service.ErrInvalidEmail), errors.Is(err, users_service.ErrInvalidRole), errors.Is(err, users_service.ErrInvalidPasswordChange):
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package users_service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLocalAccountOperationsDoNotChangeUsers(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
run func(*UsersService) error
|
||||||
|
}{
|
||||||
|
{"signup", func(s *UsersService) error {
|
||||||
|
return s.AddUser(context.Background(), "Local", "local@example.com", true, true, "", "")
|
||||||
|
}},
|
||||||
|
{"password reset", func(s *UsersService) error {
|
||||||
|
return s.RefreshPassword(context.Background(), "local@example.com")
|
||||||
|
}},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
repo := newFakeRepo()
|
||||||
|
repo.seed("local@example.com", "unchanged-hash")
|
||||||
|
s := &UsersService{usersRepo: repo}
|
||||||
|
if err := test.run(s); !errors.Is(err, ErrEmailUnavailable) {
|
||||||
|
t.Fatalf("error = %v", err)
|
||||||
|
}
|
||||||
|
user, err := repo.GetUserByEmail(context.Background(), "local@example.com")
|
||||||
|
if err != nil || repo.userCount() != 1 || user.PasswordHash != "unchanged-hash" {
|
||||||
|
t.Fatalf("account changed: user=%v, error=%v", user, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -76,6 +76,8 @@ func NewUsersService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// ErrEmailUnavailable indicates that email-based account operations are disabled.
|
||||||
|
ErrEmailUnavailable = errors.New("почтовые операции отключены на локальной площадке; обратитесь к организатору")
|
||||||
ErrTermsNotAccepted = errors.New("необходимо принять пользовательское соглашение и политику конфиденциальности")
|
ErrTermsNotAccepted = errors.New("необходимо принять пользовательское соглашение и политику конфиденциальности")
|
||||||
// ErrInvalidEmail indicates that an email address is invalid.
|
// ErrInvalidEmail indicates that an email address is invalid.
|
||||||
ErrInvalidEmail = errors.New("некорректный email")
|
ErrInvalidEmail = errors.New("некорректный email")
|
||||||
@@ -99,6 +101,9 @@ func (s *UsersService) AddUser(
|
|||||||
ip string,
|
ip string,
|
||||||
userAgent string,
|
userAgent string,
|
||||||
) error {
|
) error {
|
||||||
|
if s.emailSender == nil {
|
||||||
|
return ErrEmailUnavailable
|
||||||
|
}
|
||||||
if !acceptTerms || !acceptPrivacy {
|
if !acceptTerms || !acceptPrivacy {
|
||||||
return ErrTermsNotAccepted
|
return ErrTermsNotAccepted
|
||||||
}
|
}
|
||||||
@@ -184,6 +189,9 @@ func (s *UsersService) RefreshPassword(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
email string,
|
email string,
|
||||||
) error {
|
) error {
|
||||||
|
if s.emailSender == nil {
|
||||||
|
return ErrEmailUnavailable
|
||||||
|
}
|
||||||
email = strings.TrimSpace(email)
|
email = strings.TrimSpace(email)
|
||||||
if err := validateEmail(email); err != nil {
|
if err := validateEmail(email); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
Reference in New Issue
Block a user