add mailer and update control fields
This commit is contained in:
parent
2e3a977530
commit
fc30d04ccb
@ -1,9 +0,0 @@
|
||||
{
|
||||
"id": 1,
|
||||
"world_size": 200,
|
||||
"open": 1,
|
||||
"admin_email": "",
|
||||
"class_1_name": "Mage",
|
||||
"class_2_name": "Warrior",
|
||||
"class_3_name": "Paladin"
|
||||
}
|
@ -15,13 +15,15 @@ var (
|
||||
|
||||
// Control represents the game control settings
|
||||
type Control struct {
|
||||
ID int `json:"id"`
|
||||
WorldSize int `json:"world_size"`
|
||||
Open int `json:"open"`
|
||||
AdminEmail string `json:"admin_email"`
|
||||
Class1Name string `json:"class_1_name"`
|
||||
Class2Name string `json:"class_2_name"`
|
||||
Class3Name string `json:"class_3_name"`
|
||||
WorldSize int `json:"world_size"`
|
||||
Open int `json:"open"`
|
||||
AdminEmail string `json:"admin_email"`
|
||||
EmailMode string `json:"email_mode"` // "file" or "smtp"
|
||||
EmailFilePath string `json:"email_file_path"` // path for file mode
|
||||
SMTPHost string `json:"smtp_host"`
|
||||
SMTPPort string `json:"smtp_port"`
|
||||
SMTPUsername string `json:"smtp_username"`
|
||||
SMTPPassword string `json:"smtp_password"`
|
||||
}
|
||||
|
||||
// Init loads control settings from the specified JSON file
|
||||
@ -43,17 +45,13 @@ func Init(jsonFile string) error {
|
||||
if ctrl.WorldSize == 0 {
|
||||
ctrl.WorldSize = defaults.WorldSize
|
||||
}
|
||||
if ctrl.Class1Name == "" {
|
||||
ctrl.Class1Name = defaults.Class1Name
|
||||
if ctrl.EmailMode == "" {
|
||||
ctrl.EmailMode = defaults.EmailMode
|
||||
}
|
||||
if ctrl.Class2Name == "" {
|
||||
ctrl.Class2Name = defaults.Class2Name
|
||||
}
|
||||
if ctrl.Class3Name == "" {
|
||||
ctrl.Class3Name = defaults.Class3Name
|
||||
if ctrl.EmailFilePath == "" {
|
||||
ctrl.EmailFilePath = defaults.EmailFilePath
|
||||
}
|
||||
|
||||
ctrl.ID = 1 // Ensure singleton ID
|
||||
global = &ctrl
|
||||
} else {
|
||||
// Create default control settings if file doesn't exist
|
||||
@ -95,13 +93,15 @@ func Save() error {
|
||||
// New creates a new Control with sensible defaults
|
||||
func New() *Control {
|
||||
return &Control{
|
||||
ID: 1, // Singleton
|
||||
WorldSize: 200,
|
||||
Open: 1,
|
||||
AdminEmail: "",
|
||||
Class1Name: "Mage",
|
||||
Class2Name: "Warrior",
|
||||
Class3Name: "Paladin",
|
||||
WorldSize: 200,
|
||||
Open: 1,
|
||||
AdminEmail: "",
|
||||
EmailMode: "file",
|
||||
EmailFilePath: "emails.txt",
|
||||
SMTPHost: "",
|
||||
SMTPPort: "587",
|
||||
SMTPUsername: "",
|
||||
SMTPPassword: "",
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,7 +120,6 @@ func Set(control *Control) error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
control.ID = 1 // Ensure it's always ID 1 (singleton)
|
||||
if err := control.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -171,14 +170,19 @@ func (c *Control) Validate() error {
|
||||
if c.Open != 0 && c.Open != 1 {
|
||||
return fmt.Errorf("Open must be 0 or 1")
|
||||
}
|
||||
if c.Class1Name == "" {
|
||||
return fmt.Errorf("Class1Name cannot be empty")
|
||||
if c.EmailMode != "file" && c.EmailMode != "smtp" {
|
||||
return fmt.Errorf("EmailMode must be 'file' or 'smtp'")
|
||||
}
|
||||
if c.Class2Name == "" {
|
||||
return fmt.Errorf("Class2Name cannot be empty")
|
||||
}
|
||||
if c.Class3Name == "" {
|
||||
return fmt.Errorf("Class3Name cannot be empty")
|
||||
if c.EmailMode == "smtp" {
|
||||
if c.SMTPHost == "" {
|
||||
return fmt.Errorf("SMTPHost required when EmailMode is 'smtp'")
|
||||
}
|
||||
if c.SMTPUsername == "" {
|
||||
return fmt.Errorf("SMTPUsername required when EmailMode is 'smtp'")
|
||||
}
|
||||
if c.SMTPPassword == "" {
|
||||
return fmt.Errorf("SMTPPassword required when EmailMode is 'smtp'")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -199,62 +203,19 @@ func SetOpen(open bool) error {
|
||||
})
|
||||
}
|
||||
|
||||
// GetClassNames returns all class names as a slice
|
||||
func (c *Control) GetClassNames() []string {
|
||||
classes := make([]string, 0, 3)
|
||||
if c.Class1Name != "" {
|
||||
classes = append(classes, c.Class1Name)
|
||||
}
|
||||
if c.Class2Name != "" {
|
||||
classes = append(classes, c.Class2Name)
|
||||
}
|
||||
if c.Class3Name != "" {
|
||||
classes = append(classes, c.Class3Name)
|
||||
}
|
||||
return classes
|
||||
}
|
||||
|
||||
// GetClassName returns the name of a specific class (1-3)
|
||||
func (c *Control) GetClassName(classNum int) string {
|
||||
switch classNum {
|
||||
case 1:
|
||||
return c.Class1Name
|
||||
case 2:
|
||||
return c.Class2Name
|
||||
case 3:
|
||||
return c.Class3Name
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// IsValidClassName returns true if the given name matches one of the configured classes
|
||||
func (c *Control) IsValidClassName(name string) bool {
|
||||
if name == "" {
|
||||
return false
|
||||
}
|
||||
return name == c.Class1Name || name == c.Class2Name || name == c.Class3Name
|
||||
}
|
||||
|
||||
// GetClassNumber returns the class number (1-3) for a given class name, or 0 if not found
|
||||
func (c *Control) GetClassNumber(name string) int {
|
||||
if name == c.Class1Name && name != "" {
|
||||
return 1
|
||||
}
|
||||
if name == c.Class2Name && name != "" {
|
||||
return 2
|
||||
}
|
||||
if name == c.Class3Name && name != "" {
|
||||
return 3
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// HasAdminEmail returns true if an admin email is configured
|
||||
func (c *Control) HasAdminEmail() bool {
|
||||
return c.AdminEmail != ""
|
||||
}
|
||||
|
||||
// IsEmailConfigured returns true if email is properly configured
|
||||
func (c *Control) IsEmailConfigured() bool {
|
||||
if c.EmailMode == "file" {
|
||||
return c.EmailFilePath != ""
|
||||
}
|
||||
return c.SMTPHost != "" && c.SMTPUsername != "" && c.SMTPPassword != ""
|
||||
}
|
||||
|
||||
// IsWorldSizeValid returns true if the world size is within reasonable bounds
|
||||
func (c *Control) IsWorldSizeValid() bool {
|
||||
return c.WorldSize > 0 && c.WorldSize <= 10000
|
||||
|
132
internal/helpers/email/emailer.go
Normal file
132
internal/helpers/email/emailer.go
Normal file
@ -0,0 +1,132 @@
|
||||
package email
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
global *Emailer
|
||||
mu sync.RWMutex
|
||||
)
|
||||
|
||||
type Emailer struct {
|
||||
mode string
|
||||
filePath string
|
||||
host string
|
||||
port string
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
type Email struct {
|
||||
From string
|
||||
To []string
|
||||
Subject string
|
||||
Body string
|
||||
}
|
||||
|
||||
// Init initializes the global emailer with the provided settings
|
||||
func Init(mode, filePath, host, port, username, password string) error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
emailer := &Emailer{
|
||||
mode: mode,
|
||||
filePath: filePath,
|
||||
host: host,
|
||||
port: port,
|
||||
username: username,
|
||||
password: password,
|
||||
}
|
||||
|
||||
if err := emailer.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
global = emailer
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the global emailer instance (thread-safe)
|
||||
func Get() *Emailer {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
if global == nil {
|
||||
panic("email not initialized - call Init first")
|
||||
}
|
||||
return global
|
||||
}
|
||||
|
||||
// Send sends an email using the configured method (thread-safe)
|
||||
func Send(email Email) error {
|
||||
emailer := Get()
|
||||
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
|
||||
switch emailer.mode {
|
||||
case "file":
|
||||
return emailer.sendToFile(email)
|
||||
case "smtp":
|
||||
return emailer.sendViaSMTP(email)
|
||||
default:
|
||||
return fmt.Errorf("invalid email mode: %s", emailer.mode)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Emailer) validate() error {
|
||||
if e.mode != "file" && e.mode != "smtp" {
|
||||
return fmt.Errorf("mode must be 'file' or 'smtp'")
|
||||
}
|
||||
if e.mode == "smtp" {
|
||||
if e.host == "" || e.username == "" || e.password == "" {
|
||||
return fmt.Errorf("smtp mode requires host, username, and password")
|
||||
}
|
||||
}
|
||||
if e.mode == "file" && e.filePath == "" {
|
||||
return fmt.Errorf("file mode requires filePath")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Emailer) sendToFile(email Email) error {
|
||||
content := fmt.Sprintf(`--- EMAIL ---
|
||||
Time: %s
|
||||
From: %s
|
||||
To: %s
|
||||
Subject: %s
|
||||
|
||||
%s
|
||||
|
||||
--- END EMAIL ---
|
||||
|
||||
`, time.Now().Format(time.RFC3339), email.From, strings.Join(email.To, ", "), email.Subject, email.Body)
|
||||
|
||||
f, err := os.OpenFile(e.filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString(content)
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *Emailer) sendViaSMTP(email Email) error {
|
||||
auth := smtp.PlainAuth("", e.username, e.password, e.host)
|
||||
|
||||
msg := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s",
|
||||
email.From,
|
||||
strings.Join(email.To, ", "),
|
||||
email.Subject,
|
||||
email.Body,
|
||||
)
|
||||
|
||||
addr := e.host + ":" + e.port
|
||||
return smtp.SendMail(addr, auth, email.From, email.To, []byte(msg))
|
||||
}
|
@ -836,7 +836,7 @@ func (t *Template) getStructField(obj any, fieldName string) any {
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(obj)
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
if rv.Kind() == reflect.Pointer {
|
||||
if rv.IsNil() {
|
||||
return nil
|
||||
}
|
||||
@ -976,7 +976,7 @@ func (t *Template) isTruthy(value any) bool {
|
||||
switch rv.Kind() {
|
||||
case reflect.Slice, reflect.Array, reflect.Map:
|
||||
return rv.Len() > 0
|
||||
case reflect.Ptr:
|
||||
case reflect.Pointer:
|
||||
return !rv.IsNil()
|
||||
}
|
||||
return true
|
||||
|
Loading…
x
Reference in New Issue
Block a user