Spaces:
Build error
Build error
| package services | |
| import ( | |
| "bytes" | |
| "encoding/json" | |
| "errors" | |
| "fmt" | |
| "io" | |
| "math/rand" | |
| "net/http" | |
| "tempmail-service/config" | |
| "tempmail-service/database" | |
| "tempmail-service/models" | |
| "time" | |
| "github.com/google/uuid" | |
| ) | |
| type MailService struct { | |
| baseURL string | |
| httpClient *http.Client | |
| } | |
| func NewMailService() *MailService { | |
| cfg := config.Load() | |
| return &MailService{ | |
| baseURL: cfg.APIBaseURL, | |
| httpClient: &http.Client{ | |
| Timeout: 30 * time.Second, | |
| }, | |
| } | |
| } | |
| // GetDomains fetches available domains | |
| func (s *MailService) GetDomains() ([]models.Domain, error) { | |
| resp, err := s.httpClient.Get(s.baseURL + "/domains") | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer resp.Body.Close() | |
| if resp.StatusCode != http.StatusOK { | |
| return nil, fmt.Errorf("failed to fetch domains: status %d", resp.StatusCode) | |
| } | |
| var domainResp models.DomainResponse | |
| if err := json.NewDecoder(resp.Body).Decode(&domainResp); err != nil { | |
| return nil, err | |
| } | |
| // Filter active domains | |
| var activeDomains []models.Domain | |
| for _, d := range domainResp.HydraMember { | |
| if d.IsActive && !d.IsPrivate { | |
| activeDomains = append(activeDomains, d) | |
| } | |
| } | |
| return activeDomains, nil | |
| } | |
| // CreateAccount creates a new email account | |
| func (s *MailService) CreateAccount(address, password string) (*models.AccountResponse, error) { | |
| reqBody := models.AccountRequest{ | |
| Address: address, | |
| Password: password, | |
| } | |
| jsonBody, err := json.Marshal(reqBody) | |
| if err != nil { | |
| return nil, err | |
| } | |
| req, err := http.NewRequest("POST", s.baseURL+"/accounts", bytes.NewBuffer(jsonBody)) | |
| if err != nil { | |
| return nil, err | |
| } | |
| req.Header.Set("Content-Type", "application/json") | |
| resp, err := s.httpClient.Do(req) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer resp.Body.Close() | |
| if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK { | |
| body, _ := io.ReadAll(resp.Body) | |
| return nil, fmt.Errorf("failed to create account: status %d, body: %s", resp.StatusCode, string(body)) | |
| } | |
| var account models.AccountResponse | |
| if err := json.NewDecoder(resp.Body).Decode(&account); err != nil { | |
| return nil, err | |
| } | |
| return &account, nil | |
| } | |
| // GetToken gets authentication token | |
| func (s *MailService) GetToken(address, password string) (*models.TokenResponse, error) { | |
| reqBody := models.TokenRequest{ | |
| Address: address, | |
| Password: password, | |
| } | |
| jsonBody, err := json.Marshal(reqBody) | |
| if err != nil { | |
| return nil, err | |
| } | |
| req, err := http.NewRequest("POST", s.baseURL+"/token", bytes.NewBuffer(jsonBody)) | |
| if err != nil { | |
| return nil, err | |
| } | |
| req.Header.Set("Content-Type", "application/json") | |
| resp, err := s.httpClient.Do(req) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer resp.Body.Close() | |
| if resp.StatusCode != http.StatusOK { | |
| body, _ := io.ReadAll(resp.Body) | |
| return nil, fmt.Errorf("failed to get token: status %d, body: %s", resp.StatusCode, string(body)) | |
| } | |
| var token models.TokenResponse | |
| if err := json.NewDecoder(resp.Body).Decode(&token); err != nil { | |
| return nil, err | |
| } | |
| return &token, nil | |
| } | |
| // GetMessages fetches all messages for an account | |
| func (s *MailService) GetMessages(token string, page int) (*models.MessagesResponse, error) { | |
| url := fmt.Sprintf("%s/messages?page=%d", s.baseURL, page) | |
| req, err := http.NewRequest("GET", url, nil) | |
| if err != nil { | |
| return nil, err | |
| } | |
| req.Header.Set("Authorization", "Bearer "+token) | |
| resp, err := s.httpClient.Do(req) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer resp.Body.Close() | |
| if resp.StatusCode == http.StatusUnauthorized { | |
| return nil, errors.New("unauthorized: invalid token") | |
| } | |
| if resp.StatusCode != http.StatusOK { | |
| return nil, fmt.Errorf("failed to fetch messages: status %d", resp.StatusCode) | |
| } | |
| var messages models.MessagesResponse | |
| if err := json.NewDecoder(resp.Body).Decode(&messages); err != nil { | |
| return nil, err | |
| } | |
| return &messages, nil | |
| } | |
| // GetMessage fetches a specific message | |
| func (s *MailService) GetMessage(token, messageID string) (*models.MessageDetail, error) { | |
| req, err := http.NewRequest("GET", s.baseURL+"/messages/"+messageID, nil) | |
| if err != nil { | |
| return nil, err | |
| } | |
| req.Header.Set("Authorization", "Bearer "+token) | |
| resp, err := s.httpClient.Do(req) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer resp.Body.Close() | |
| if resp.StatusCode == http.StatusUnauthorized { | |
| return nil, errors.New("unauthorized: invalid token") | |
| } | |
| if resp.StatusCode == http.StatusNotFound { | |
| return nil, errors.New("message not found") | |
| } | |
| if resp.StatusCode != http.StatusOK { | |
| return nil, fmt.Errorf("failed to fetch message: status %d", resp.StatusCode) | |
| } | |
| var message models.MessageDetail | |
| if err := json.NewDecoder(resp.Body).Decode(&message); err != nil { | |
| return nil, err | |
| } | |
| return &message, nil | |
| } | |
| // DeleteMessage deletes a specific message | |
| func (s *MailService) DeleteMessage(token, messageID string) error { | |
| req, err := http.NewRequest("DELETE", s.baseURL+"/messages/"+messageID, nil) | |
| if err != nil { | |
| return err | |
| } | |
| req.Header.Set("Authorization", "Bearer "+token) | |
| resp, err := s.httpClient.Do(req) | |
| if err != nil { | |
| return err | |
| } | |
| defer resp.Body.Close() | |
| if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK { | |
| return fmt.Errorf("failed to delete message: status %d", resp.StatusCode) | |
| } | |
| return nil | |
| } | |
| // MarkAsRead marks a message as read | |
| func (s *MailService) MarkAsRead(token, messageID string) error { | |
| req, err := http.NewRequest("PATCH", s.baseURL+"/messages/"+messageID, nil) | |
| if err != nil { | |
| return err | |
| } | |
| req.Header.Set("Authorization", "Bearer "+token) | |
| resp, err := s.httpClient.Do(req) | |
| if err != nil { | |
| return err | |
| } | |
| defer resp.Body.Close() | |
| if resp.StatusCode != http.StatusOK { | |
| return fmt.Errorf("failed to mark as read: status %d", resp.StatusCode) | |
| } | |
| return nil | |
| } | |
| // DeleteAccount deletes an account | |
| func (s *MailService) DeleteAccount(token, accountID string) error { | |
| req, err := http.NewRequest("DELETE", s.baseURL+"/accounts/"+accountID, nil) | |
| if err != nil { | |
| return err | |
| } | |
| req.Header.Set("Authorization", "Bearer "+token) | |
| resp, err := s.httpClient.Do(req) | |
| if err != nil { | |
| return err | |
| } | |
| defer resp.Body.Close() | |
| if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK { | |
| return fmt.Errorf("failed to delete account: status %d", resp.StatusCode) | |
| } | |
| return nil | |
| } | |
| // GetAccountInfo gets account information | |
| func (s *MailService) GetAccountInfo(token string) (*models.AccountResponse, error) { | |
| req, err := http.NewRequest("GET", s.baseURL+"/me", nil) | |
| if err != nil { | |
| return nil, err | |
| } | |
| req.Header.Set("Authorization", "Bearer "+token) | |
| resp, err := s.httpClient.Do(req) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer resp.Body.Close() | |
| if resp.StatusCode != http.StatusOK { | |
| return nil, fmt.Errorf("failed to get account info: status %d", resp.StatusCode) | |
| } | |
| var account models.AccountResponse | |
| if err := json.NewDecoder(resp.Body).Decode(&account); err != nil { | |
| return nil, err | |
| } | |
| return &account, nil | |
| } | |
| // Session Management | |
| // CreateSession creates a new email session | |
| func (s *MailService) CreateSession(username, domain string) (*models.Session, error) { | |
| // Generate password | |
| password := generatePassword(16) | |
| address := username + "@" + domain | |
| // Create account | |
| account, err := s.CreateAccount(address, password) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to create account: %w", err) | |
| } | |
| // Get token | |
| token, err := s.GetToken(address, password) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to get token: %w", err) | |
| } | |
| // Create session | |
| session := &models.Session{ | |
| SessionID: uuid.New().String(), | |
| AccountID: account.ID, | |
| Email: address, | |
| Password: password, | |
| Token: token.Token, | |
| ExpiresAt: time.Now().Add(24 * time.Hour), | |
| } | |
| // Save to database | |
| if err := database.DB.Create(session).Error; err != nil { | |
| return nil, fmt.Errorf("failed to save session: %w", err) | |
| } | |
| return session, nil | |
| } | |
| // GetSession retrieves a session by ID | |
| func (s *MailService) GetSession(sessionID string) (*models.Session, error) { | |
| var session models.Session | |
| if err := database.DB.Where("session_id = ?", sessionID).First(&session).Error; err != nil { | |
| return nil, errors.New("session not found") | |
| } | |
| if time.Now().After(session.ExpiresAt) { | |
| return nil, errors.New("session expired") | |
| } | |
| return &session, nil | |
| } | |
| // DeleteSession deletes a session and its account | |
| func (s *MailService) DeleteSession(sessionID string) error { | |
| session, err := s.GetSession(sessionID) | |
| if err != nil { | |
| return err | |
| } | |
| // Delete from API | |
| _ = s.DeleteAccount(session.Token, session.AccountID) | |
| // Delete from database | |
| database.DB.Where("session_id = ?", sessionID).Delete(&models.CachedMessage{}) | |
| database.DB.Where("session_id = ?", sessionID).Delete(&models.Session{}) | |
| return nil | |
| } | |
| // RefreshToken refreshes the session token | |
| func (s *MailService) RefreshToken(session *models.Session) error { | |
| token, err := s.GetToken(session.Email, session.Password) | |
| if err != nil { | |
| return err | |
| } | |
| session.Token = token.Token | |
| return database.DB.Save(session).Error | |
| } | |
| // Helper function to generate random password | |
| func generatePassword(length int) string { | |
| const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%" | |
| seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) | |
| b := make([]byte, length) | |
| for i := range b { | |
| b[i] = charset[seededRand.Intn(len(charset))] | |
| } | |
| return string(b) | |
| } | |
| // GenerateUsername generates a random username | |
| func GenerateUsername() string { | |
| const charset = "abcdefghijklmnopqrstuvwxyz0123456789" | |
| seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) | |
| length := 8 + seededRand.Intn(5) // 8-12 characters | |
| b := make([]byte, length) | |
| for i := range b { | |
| b[i] = charset[seededRand.Intn(len(charset))] | |
| } | |
| return string(b) | |
| } |