一阶段ok
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"mail_go/internal/db"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AttachmentStore defines the interface for attachment data operations.
|
||||
type AttachmentStore interface {
|
||||
Create(att *db.Attachment) error
|
||||
GetByID(id uint) (*db.Attachment, error)
|
||||
ListByMessage(messageID uint) ([]db.Attachment, error)
|
||||
Delete(id uint) error
|
||||
DeleteByMessage(messageID uint) error
|
||||
}
|
||||
|
||||
// attachmentStoreGorm implements AttachmentStore using GORM.
|
||||
type attachmentStoreGorm struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// newAttachmentStore creates a new GORM-backed AttachmentStore.
|
||||
func newAttachmentStore(database *gorm.DB) AttachmentStore {
|
||||
return &attachmentStoreGorm{db: database}
|
||||
}
|
||||
|
||||
// Create inserts a new attachment record.
|
||||
func (s *attachmentStoreGorm) Create(att *db.Attachment) error {
|
||||
return s.db.Create(att).Error
|
||||
}
|
||||
|
||||
// GetByID retrieves an attachment by primary key.
|
||||
func (s *attachmentStoreGorm) GetByID(id uint) (*db.Attachment, error) {
|
||||
var att db.Attachment
|
||||
if err := s.db.First(&att, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &att, nil
|
||||
}
|
||||
|
||||
// ListByMessage retrieves all attachments for a given message.
|
||||
func (s *attachmentStoreGorm) ListByMessage(messageID uint) ([]db.Attachment, error) {
|
||||
var attachments []db.Attachment
|
||||
if err := s.db.Where("message_id = ?", messageID).Find(&attachments).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return attachments, nil
|
||||
}
|
||||
|
||||
// Delete removes an attachment by ID.
|
||||
func (s *attachmentStoreGorm) Delete(id uint) error {
|
||||
return s.db.Delete(&db.Attachment{}, id).Error
|
||||
}
|
||||
|
||||
// DeleteByMessage removes all attachments for a given message.
|
||||
func (s *attachmentStoreGorm) DeleteByMessage(messageID uint) error {
|
||||
return s.db.Where("message_id = ?", messageID).Delete(&db.Attachment{}).Error
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"mail_go/internal/db"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// DomainStore defines the interface for domain data operations.
|
||||
type DomainStore interface {
|
||||
Create(domain *db.Domain) error
|
||||
GetByID(id uint) (*db.Domain, error)
|
||||
GetByName(name string) (*db.Domain, error)
|
||||
Update(domain *db.Domain) error
|
||||
Delete(id uint) error
|
||||
List(page, size int) ([]db.Domain, int64, error)
|
||||
}
|
||||
|
||||
// domainStoreGorm implements DomainStore using GORM.
|
||||
type domainStoreGorm struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// newDomainStore creates a new GORM-backed DomainStore.
|
||||
func newDomainStore(database *gorm.DB) DomainStore {
|
||||
return &domainStoreGorm{db: database}
|
||||
}
|
||||
|
||||
// Create inserts a new domain record.
|
||||
func (s *domainStoreGorm) Create(domain *db.Domain) error {
|
||||
return s.db.Create(domain).Error
|
||||
}
|
||||
|
||||
// GetByID retrieves a domain by primary key.
|
||||
func (s *domainStoreGorm) GetByID(id uint) (*db.Domain, error) {
|
||||
var domain db.Domain
|
||||
if err := s.db.First(&domain, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &domain, nil
|
||||
}
|
||||
|
||||
// GetByName retrieves a domain by its name.
|
||||
func (s *domainStoreGorm) GetByName(name string) (*db.Domain, error) {
|
||||
var domain db.Domain
|
||||
if err := s.db.Where("name = ?", name).First(&domain).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &domain, nil
|
||||
}
|
||||
|
||||
// Update saves changes to an existing domain record.
|
||||
func (s *domainStoreGorm) Update(domain *db.Domain) error {
|
||||
return s.db.Save(domain).Error
|
||||
}
|
||||
|
||||
// Delete removes a domain by ID.
|
||||
func (s *domainStoreGorm) Delete(id uint) error {
|
||||
return s.db.Delete(&db.Domain{}, id).Error
|
||||
}
|
||||
|
||||
// List retrieves a paginated list of domains.
|
||||
func (s *domainStoreGorm) List(page, size int) ([]db.Domain, int64, error) {
|
||||
var domains []db.Domain
|
||||
var total int64
|
||||
|
||||
if err := s.db.Model(&db.Domain{}).Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (page - 1) * size
|
||||
if err := s.db.Offset(offset).Limit(size).Find(&domains).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return domains, total, nil
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"mail_go/internal/db"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Common store errors
|
||||
var (
|
||||
ErrInvalidEmail = errors.New("无效的邮箱地址格式")
|
||||
ErrInvalidCredentials = errors.New("用户名或密码错误")
|
||||
ErrUserInactive = errors.New("用户已被禁用")
|
||||
ErrRecordNotFound = errors.New("记录不存在")
|
||||
)
|
||||
|
||||
// MailStore defines the interface for mail message operations.
|
||||
type MailStore interface {
|
||||
Create(msg *db.Message) error
|
||||
GetByID(id uint) (*db.Message, error)
|
||||
ListByUserAndFolder(userID uint, folder string, page, size int) ([]db.Message, int64, error)
|
||||
ListAllByUserAndFolder(userID uint, folder string) ([]db.Message, error)
|
||||
CountByUserAndFolder(userID uint, folder string) (int64, error)
|
||||
MarkRead(id uint) error
|
||||
MarkFlagged(id uint, flagged bool) error
|
||||
MoveToFolder(id uint, folder string) error
|
||||
Delete(id uint) error
|
||||
CountUnread(userID uint, folder string) (int64, error)
|
||||
}
|
||||
|
||||
// mailStoreGorm implements MailStore using GORM.
|
||||
type mailStoreGorm struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// newMailStore creates a new GORM-backed MailStore.
|
||||
func newMailStore(database *gorm.DB) MailStore {
|
||||
return &mailStoreGorm{db: database}
|
||||
}
|
||||
|
||||
// Create inserts a new message record.
|
||||
func (s *mailStoreGorm) Create(msg *db.Message) error {
|
||||
return s.db.Create(msg).Error
|
||||
}
|
||||
|
||||
// GetByID retrieves a message by primary key.
|
||||
func (s *mailStoreGorm) GetByID(id uint) (*db.Message, error) {
|
||||
var msg db.Message
|
||||
if err := s.db.First(&msg, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &msg, nil
|
||||
}
|
||||
|
||||
// ListByUserAndFolder retrieves a paginated list of messages for a user and folder.
|
||||
func (s *mailStoreGorm) ListByUserAndFolder(userID uint, folder string, page, size int) ([]db.Message, int64, error) {
|
||||
var messages []db.Message
|
||||
var total int64
|
||||
|
||||
query := s.db.Where("user_id = ? AND folder = ?", userID, folder)
|
||||
if err := query.Model(&db.Message{}).Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (page - 1) * size
|
||||
if err := query.Order("date DESC").Offset(offset).Limit(size).Find(&messages).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return messages, total, nil
|
||||
}
|
||||
|
||||
// MarkRead sets the IsRead flag to true for a message.
|
||||
func (s *mailStoreGorm) MarkRead(id uint) error {
|
||||
return s.db.Model(&db.Message{}).Where("id = ?", id).Update("is_read", true).Error
|
||||
}
|
||||
|
||||
// MarkFlagged sets the IsFlagged flag for a message.
|
||||
func (s *mailStoreGorm) MarkFlagged(id uint, flagged bool) error {
|
||||
return s.db.Model(&db.Message{}).Where("id = ?", id).Update("is_flagged", flagged).Error
|
||||
}
|
||||
|
||||
// MoveToFolder changes the folder of a message.
|
||||
func (s *mailStoreGorm) MoveToFolder(id uint, folder string) error {
|
||||
return s.db.Model(&db.Message{}).Where("id = ?", id).Update("folder", folder).Error
|
||||
}
|
||||
|
||||
// Delete removes a message by ID.
|
||||
func (s *mailStoreGorm) Delete(id uint) error {
|
||||
return s.db.Delete(&db.Message{}, id).Error
|
||||
}
|
||||
|
||||
// CountUnread returns the count of unread messages for a user in a folder.
|
||||
func (s *mailStoreGorm) CountUnread(userID uint, folder string) (int64, error) {
|
||||
var count int64
|
||||
if err := s.db.Model(&db.Message{}).
|
||||
Where("user_id = ? AND folder = ? AND is_read = ?", userID, folder, false).
|
||||
Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// ListAllByUserAndFolder retrieves all messages for a user in a folder without pagination.
|
||||
// Messages are ordered by ID ascending so that sequence numbers are stable.
|
||||
func (s *mailStoreGorm) ListAllByUserAndFolder(userID uint, folder string) ([]db.Message, error) {
|
||||
var messages []db.Message
|
||||
if err := s.db.Where("user_id = ? AND folder = ?", userID, folder).
|
||||
Order("id ASC").Find(&messages).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
// CountByUserAndFolder returns the total count of messages for a user in a folder.
|
||||
func (s *mailStoreGorm) CountByUserAndFolder(userID uint, folder string) (int64, error) {
|
||||
var count int64
|
||||
if err := s.db.Model(&db.Message{}).
|
||||
Where("user_id = ? AND folder = ?", userID, folder).
|
||||
Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"mail_go/internal/db"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Stores aggregates all store interfaces for convenient access.
|
||||
type Stores struct {
|
||||
Users UserStore
|
||||
Mails MailStore
|
||||
Domains DomainStore
|
||||
Attachments AttachmentStore
|
||||
}
|
||||
|
||||
// NewStores creates a new Stores instance with all GORM-backed implementations.
|
||||
func NewStores(database *gorm.DB) *Stores {
|
||||
return &Stores{
|
||||
Users: newUserStore(database),
|
||||
Mails: newMailStore(database),
|
||||
Domains: newDomainStore(database),
|
||||
Attachments: newAttachmentStore(database),
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure models are referenced (prevents unused import errors).
|
||||
var _ = db.User{}
|
||||
var _ = db.Domain{}
|
||||
var _ = db.Message{}
|
||||
var _ = db.Attachment{}
|
||||
@@ -0,0 +1,146 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"mail_go/internal/db"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserStore defines the interface for user data operations.
|
||||
type UserStore interface {
|
||||
Create(user *db.User) error
|
||||
GetByID(id uint) (*db.User, error)
|
||||
GetByUsername(username string, domainID uint) (*db.User, error)
|
||||
GetByEmail(email string) (*db.User, error)
|
||||
Authenticate(email, password string) (*db.User, error)
|
||||
Update(user *db.User) error
|
||||
Delete(id uint) error
|
||||
List(domainID uint, page, size int) ([]db.User, int64, error)
|
||||
ListAll(page, size int) ([]db.User, int64, error)
|
||||
UpdateUsedBytes(id uint, delta int64) error
|
||||
UpdatePassword(userID uint, hashedPassword string) error
|
||||
}
|
||||
|
||||
// userStoreGorm implements UserStore using GORM.
|
||||
type userStoreGorm struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// newUserStore creates a new GORM-backed UserStore.
|
||||
func newUserStore(database *gorm.DB) UserStore {
|
||||
return &userStoreGorm{db: database}
|
||||
}
|
||||
|
||||
// Create inserts a new user record.
|
||||
func (s *userStoreGorm) Create(user *db.User) error {
|
||||
return s.db.Create(user).Error
|
||||
}
|
||||
|
||||
// GetByID retrieves a user by primary key.
|
||||
func (s *userStoreGorm) GetByID(id uint) (*db.User, error) {
|
||||
var user db.User
|
||||
if err := s.db.Preload("Domain").First(&user, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetByUsername retrieves a user by username and domain ID.
|
||||
func (s *userStoreGorm) GetByUsername(username string, domainID uint) (*db.User, error) {
|
||||
var user db.User
|
||||
if err := s.db.Where("username = ? AND domain_id = ?", username, domainID).First(&user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetByEmail retrieves a user by email address (user@domain format).
|
||||
func (s *userStoreGorm) GetByEmail(email string) (*db.User, error) {
|
||||
parts := strings.SplitN(email, "@", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, ErrInvalidEmail
|
||||
}
|
||||
username := parts[0]
|
||||
domainName := parts[1]
|
||||
|
||||
var user db.User
|
||||
if err := s.db.Joins("JOIN domains ON domains.id = users.domain_id").
|
||||
Where("users.username = ? AND domains.name = ?", username, domainName).
|
||||
Preload("Domain").
|
||||
First(&user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// Authenticate verifies an email/password combination and returns the user on success.
|
||||
func (s *userStoreGorm) Authenticate(email, password string) (*db.User, error) {
|
||||
user, err := s.GetByEmail(email)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidCredentials
|
||||
}
|
||||
if !user.IsActive {
|
||||
return nil, ErrUserInactive
|
||||
}
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
|
||||
return nil, ErrInvalidCredentials
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// Update saves changes to an existing user record.
|
||||
func (s *userStoreGorm) Update(user *db.User) error {
|
||||
return s.db.Save(user).Error
|
||||
}
|
||||
|
||||
// Delete removes a user by ID (soft delete if supported, hard delete otherwise).
|
||||
func (s *userStoreGorm) Delete(id uint) error {
|
||||
return s.db.Delete(&db.User{}, id).Error
|
||||
}
|
||||
|
||||
// List retrieves a paginated list of users for a given domain.
|
||||
func (s *userStoreGorm) List(domainID uint, page, size int) ([]db.User, int64, error) {
|
||||
var users []db.User
|
||||
var total int64
|
||||
|
||||
query := s.db.Where("domain_id = ?", domainID)
|
||||
if err := query.Model(&db.User{}).Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (page - 1) * size
|
||||
if err := s.db.Preload("Domain").Where("domain_id = ?", domainID).Offset(offset).Limit(size).Find(&users).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return users, total, nil
|
||||
}
|
||||
|
||||
// UpdateUsedBytes atomically adjusts the UsedBytes field by delta.
|
||||
func (s *userStoreGorm) UpdateUsedBytes(id uint, delta int64) error {
|
||||
return s.db.Model(&db.User{}).Where("id = ?", id).
|
||||
Update("used_bytes", gorm.Expr("used_bytes + ?", delta)).Error
|
||||
}
|
||||
|
||||
// UpdatePassword updates the password hash for a user.
|
||||
func (s *userStoreGorm) UpdatePassword(userID uint, hashedPassword string) error {
|
||||
return s.db.Model(&db.User{}).Where("id = ?", userID).Update("password_hash", hashedPassword).Error
|
||||
}
|
||||
|
||||
// ListAll retrieves a paginated list of all users across all domains.
|
||||
func (s *userStoreGorm) ListAll(page, size int) ([]db.User, int64, error) {
|
||||
var users []db.User
|
||||
var total int64
|
||||
|
||||
if err := s.db.Model(&db.User{}).Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (page - 1) * size
|
||||
if err := s.db.Preload("Domain").Offset(offset).Limit(size).Find(&users).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return users, total, nil
|
||||
}
|
||||
Reference in New Issue
Block a user