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
|
// Control represents the game control settings
|
||||||
type Control struct {
|
type Control struct {
|
||||||
ID int `json:"id"`
|
WorldSize int `json:"world_size"`
|
||||||
WorldSize int `json:"world_size"`
|
Open int `json:"open"`
|
||||||
Open int `json:"open"`
|
AdminEmail string `json:"admin_email"`
|
||||||
AdminEmail string `json:"admin_email"`
|
EmailMode string `json:"email_mode"` // "file" or "smtp"
|
||||||
Class1Name string `json:"class_1_name"`
|
EmailFilePath string `json:"email_file_path"` // path for file mode
|
||||||
Class2Name string `json:"class_2_name"`
|
SMTPHost string `json:"smtp_host"`
|
||||||
Class3Name string `json:"class_3_name"`
|
SMTPPort string `json:"smtp_port"`
|
||||||
|
SMTPUsername string `json:"smtp_username"`
|
||||||
|
SMTPPassword string `json:"smtp_password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init loads control settings from the specified JSON file
|
// Init loads control settings from the specified JSON file
|
||||||
@ -43,17 +45,13 @@ func Init(jsonFile string) error {
|
|||||||
if ctrl.WorldSize == 0 {
|
if ctrl.WorldSize == 0 {
|
||||||
ctrl.WorldSize = defaults.WorldSize
|
ctrl.WorldSize = defaults.WorldSize
|
||||||
}
|
}
|
||||||
if ctrl.Class1Name == "" {
|
if ctrl.EmailMode == "" {
|
||||||
ctrl.Class1Name = defaults.Class1Name
|
ctrl.EmailMode = defaults.EmailMode
|
||||||
}
|
}
|
||||||
if ctrl.Class2Name == "" {
|
if ctrl.EmailFilePath == "" {
|
||||||
ctrl.Class2Name = defaults.Class2Name
|
ctrl.EmailFilePath = defaults.EmailFilePath
|
||||||
}
|
|
||||||
if ctrl.Class3Name == "" {
|
|
||||||
ctrl.Class3Name = defaults.Class3Name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctrl.ID = 1 // Ensure singleton ID
|
|
||||||
global = &ctrl
|
global = &ctrl
|
||||||
} else {
|
} else {
|
||||||
// Create default control settings if file doesn't exist
|
// Create default control settings if file doesn't exist
|
||||||
@ -95,13 +93,15 @@ func Save() error {
|
|||||||
// New creates a new Control with sensible defaults
|
// New creates a new Control with sensible defaults
|
||||||
func New() *Control {
|
func New() *Control {
|
||||||
return &Control{
|
return &Control{
|
||||||
ID: 1, // Singleton
|
WorldSize: 200,
|
||||||
WorldSize: 200,
|
Open: 1,
|
||||||
Open: 1,
|
AdminEmail: "",
|
||||||
AdminEmail: "",
|
EmailMode: "file",
|
||||||
Class1Name: "Mage",
|
EmailFilePath: "emails.txt",
|
||||||
Class2Name: "Warrior",
|
SMTPHost: "",
|
||||||
Class3Name: "Paladin",
|
SMTPPort: "587",
|
||||||
|
SMTPUsername: "",
|
||||||
|
SMTPPassword: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +120,6 @@ func Set(control *Control) error {
|
|||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
control.ID = 1 // Ensure it's always ID 1 (singleton)
|
|
||||||
if err := control.Validate(); err != nil {
|
if err := control.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -171,14 +170,19 @@ func (c *Control) Validate() error {
|
|||||||
if c.Open != 0 && c.Open != 1 {
|
if c.Open != 0 && c.Open != 1 {
|
||||||
return fmt.Errorf("Open must be 0 or 1")
|
return fmt.Errorf("Open must be 0 or 1")
|
||||||
}
|
}
|
||||||
if c.Class1Name == "" {
|
if c.EmailMode != "file" && c.EmailMode != "smtp" {
|
||||||
return fmt.Errorf("Class1Name cannot be empty")
|
return fmt.Errorf("EmailMode must be 'file' or 'smtp'")
|
||||||
}
|
}
|
||||||
if c.Class2Name == "" {
|
if c.EmailMode == "smtp" {
|
||||||
return fmt.Errorf("Class2Name cannot be empty")
|
if c.SMTPHost == "" {
|
||||||
}
|
return fmt.Errorf("SMTPHost required when EmailMode is 'smtp'")
|
||||||
if c.Class3Name == "" {
|
}
|
||||||
return fmt.Errorf("Class3Name cannot be empty")
|
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
|
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
|
// HasAdminEmail returns true if an admin email is configured
|
||||||
func (c *Control) HasAdminEmail() bool {
|
func (c *Control) HasAdminEmail() bool {
|
||||||
return c.AdminEmail != ""
|
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
|
// IsWorldSizeValid returns true if the world size is within reasonable bounds
|
||||||
func (c *Control) IsWorldSizeValid() bool {
|
func (c *Control) IsWorldSizeValid() bool {
|
||||||
return c.WorldSize > 0 && c.WorldSize <= 10000
|
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)
|
rv := reflect.ValueOf(obj)
|
||||||
if rv.Kind() == reflect.Ptr {
|
if rv.Kind() == reflect.Pointer {
|
||||||
if rv.IsNil() {
|
if rv.IsNil() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -976,7 +976,7 @@ func (t *Template) isTruthy(value any) bool {
|
|||||||
switch rv.Kind() {
|
switch rv.Kind() {
|
||||||
case reflect.Slice, reflect.Array, reflect.Map:
|
case reflect.Slice, reflect.Array, reflect.Map:
|
||||||
return rv.Len() > 0
|
return rv.Len() > 0
|
||||||
case reflect.Ptr:
|
case reflect.Pointer:
|
||||||
return !rv.IsNil()
|
return !rv.IsNil()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
Loading…
x
Reference in New Issue
Block a user