2025-05-07 18:01:48 +08:00
|
|
|
|
from django.db import models
|
|
|
|
|
from apps.accounts.models import User
|
2025-05-13 11:58:17 +08:00
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
from django.utils import timezone
|
2025-05-13 18:36:06 +08:00
|
|
|
|
import uuid
|
2025-05-07 18:01:48 +08:00
|
|
|
|
|
|
|
|
|
class GmailCredential(models.Model):
|
|
|
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='gmail_credentials')
|
2025-05-13 11:58:17 +08:00
|
|
|
|
email = models.EmailField(unique=True, help_text="Gmail email address")
|
|
|
|
|
credentials = models.TextField(help_text="Serialized OAuth2 credentials (JSON)")
|
|
|
|
|
is_default = models.BooleanField(default=False, help_text="Default Gmail account for user")
|
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
|
is_valid = models.BooleanField(default=True, help_text="Whether the credential is valid")
|
2025-05-13 18:36:06 +08:00
|
|
|
|
last_history_id = models.CharField(max_length=50, blank=True, null=True, help_text="Last processed Gmail history ID")
|
2025-05-13 11:58:17 +08:00
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
unique_together = ('user', 'email')
|
|
|
|
|
|
|
|
|
|
def set_credentials(self, credentials):
|
|
|
|
|
self.credentials = json.dumps({
|
|
|
|
|
'token': credentials.token,
|
|
|
|
|
'refresh_token': credentials.refresh_token,
|
|
|
|
|
'token_uri': credentials.token_uri,
|
|
|
|
|
'client_id': credentials.client_id,
|
|
|
|
|
'client_secret': credentials.client_secret,
|
|
|
|
|
'scopes': credentials.scopes
|
|
|
|
|
})
|
|
|
|
|
self.is_valid = True
|
|
|
|
|
|
|
|
|
|
def get_credentials(self):
|
|
|
|
|
from google.oauth2.credentials import Credentials
|
|
|
|
|
creds_data = json.loads(self.credentials)
|
|
|
|
|
return Credentials(
|
|
|
|
|
token=creds_data['token'],
|
|
|
|
|
refresh_token=creds_data['refresh_token'],
|
|
|
|
|
token_uri=creds_data['token_uri'],
|
|
|
|
|
client_id=creds_data['client_id'],
|
|
|
|
|
client_secret=creds_data['client_secret'],
|
|
|
|
|
scopes=creds_data['scopes']
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return f"{self.user.username} - {self.email}"
|
|
|
|
|
|
|
|
|
|
class GmailConversation(models.Model):
|
|
|
|
|
"""Gmail对话记录,跟踪用户和达人之间的邮件交互"""
|
|
|
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='gmail_conversations')
|
2025-05-20 15:57:10 +08:00
|
|
|
|
conversation_id = models.CharField(max_length=100, unique=True, help_text="Unique conversation identifier")
|
|
|
|
|
user_email = models.EmailField(help_text="User's Gmail address")
|
|
|
|
|
influencer_email = models.EmailField(help_text="Influencer's email address")
|
|
|
|
|
title = models.CharField(max_length=255, help_text="Conversation title")
|
2025-05-13 11:58:17 +08:00
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
|
updated_at = models.DateTimeField(auto_now=True)
|
2025-05-20 15:57:10 +08:00
|
|
|
|
last_sync_time = models.DateTimeField(null=True, blank=True, help_text="Last time conversation was synced with Gmail")
|
|
|
|
|
is_active = models.BooleanField(default=True, help_text="Whether this conversation is active")
|
|
|
|
|
has_sent_greeting = models.BooleanField(default=False, help_text="Whether a greeting message has been sent to this conversation")
|
|
|
|
|
metadata = models.JSONField(null=True, blank=True, help_text="Additional metadata for the conversation")
|
2025-05-07 18:01:48 +08:00
|
|
|
|
|
|
|
|
|
def __str__(self):
|
2025-05-13 11:58:17 +08:00
|
|
|
|
return f"{self.user.username}: {self.user_email} - {self.influencer_email}"
|
2025-05-07 18:01:48 +08:00
|
|
|
|
|
|
|
|
|
class Meta:
|
2025-05-13 11:58:17 +08:00
|
|
|
|
ordering = ['-updated_at']
|
|
|
|
|
unique_together = ('user', 'user_email', 'influencer_email')
|
2025-05-07 18:01:48 +08:00
|
|
|
|
|
2025-05-13 11:58:17 +08:00
|
|
|
|
class GmailAttachment(models.Model):
|
|
|
|
|
"""Gmail附件记录"""
|
|
|
|
|
conversation = models.ForeignKey(GmailConversation, on_delete=models.CASCADE, related_name='attachments')
|
|
|
|
|
email_message_id = models.CharField(max_length=100, help_text="Gmail邮件ID")
|
2025-05-20 15:57:10 +08:00
|
|
|
|
attachment_id = models.TextField(help_text="Gmail附件的唯一标识符,可能很长")
|
2025-05-13 11:58:17 +08:00
|
|
|
|
filename = models.CharField(max_length=255, help_text="原始文件名")
|
|
|
|
|
file_path = models.CharField(max_length=255, help_text="保存在服务器上的路径")
|
|
|
|
|
content_type = models.CharField(max_length=100, help_text="MIME类型")
|
|
|
|
|
size = models.IntegerField(default=0, help_text="文件大小(字节)")
|
|
|
|
|
sender_email = models.EmailField(help_text="发送者邮箱")
|
|
|
|
|
chat_message_id = models.CharField(max_length=100, blank=True, null=True, help_text="关联到ChatHistory的消息ID")
|
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
2025-05-07 18:01:48 +08:00
|
|
|
|
|
|
|
|
|
def __str__(self):
|
2025-05-13 11:58:17 +08:00
|
|
|
|
return f"{self.filename} ({self.size} bytes)"
|
|
|
|
|
|
|
|
|
|
def get_file_extension(self):
|
|
|
|
|
"""获取文件扩展名"""
|
|
|
|
|
_, ext = os.path.splitext(self.filename)
|
|
|
|
|
return ext.lower()
|
|
|
|
|
|
|
|
|
|
def get_absolute_url(self):
|
|
|
|
|
"""获取文件URL"""
|
|
|
|
|
return f"/media/gmail_attachments/{os.path.basename(self.file_path)}"
|
2025-05-07 18:01:48 +08:00
|
|
|
|
|
|
|
|
|
class Meta:
|
2025-05-13 18:36:06 +08:00
|
|
|
|
ordering = ['-created_at']
|
|
|
|
|
|
|
|
|
|
class ConversationSummary(models.Model):
|
|
|
|
|
"""Gmail对话摘要模型,用于持久化存储对话摘要"""
|
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
|
|
|
conversation = models.OneToOneField('GmailConversation', on_delete=models.CASCADE, related_name='summary')
|
|
|
|
|
content = models.TextField(verbose_name='摘要内容')
|
|
|
|
|
last_message_id = models.CharField(max_length=255, verbose_name='最后处理的消息ID或ChatHistory的ID', null=True, blank=True)
|
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
db_table = 'gmail_conversation_summaries'
|
|
|
|
|
verbose_name = 'Gmail对话摘要'
|
|
|
|
|
verbose_name_plural = 'Gmail对话摘要'
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
2025-05-20 15:57:10 +08:00
|
|
|
|
return f"对话 {self.conversation.id} 摘要"
|
|
|
|
|
|
|
|
|
|
class UserGoal(models.Model):
|
|
|
|
|
"""用户目标模型 - 存储用户设定的沟通或销售目标"""
|
|
|
|
|
STATUS_CHOICES = [
|
|
|
|
|
('pending', '待处理'),
|
|
|
|
|
('in_progress', '进行中'),
|
|
|
|
|
('completed', '已完成'),
|
|
|
|
|
('failed', '失败')
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
|
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='goals')
|
|
|
|
|
conversation = models.ForeignKey(GmailConversation, on_delete=models.CASCADE, related_name='goals', null=True)
|
|
|
|
|
description = models.TextField(verbose_name='目标描述')
|
|
|
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending', verbose_name='目标状态')
|
|
|
|
|
is_active = models.BooleanField(default=True, verbose_name='是否激活')
|
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
|
completion_time = models.DateTimeField(null=True, blank=True, verbose_name='完成时间')
|
|
|
|
|
last_activity_time = models.DateTimeField(null=True, blank=True, verbose_name='最后活动时间')
|
|
|
|
|
metadata = models.JSONField(default=dict, blank=True, null=True, help_text="存储额外信息")
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
verbose_name = '用户目标'
|
|
|
|
|
verbose_name_plural = '用户目标'
|
|
|
|
|
ordering = ['-updated_at']
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return f"{self.user.username}的目标 - {self.description[:20]}..."
|
|
|
|
|
|
|
|
|
|
def get_conversation_id(self):
|
|
|
|
|
"""获取对话ID"""
|
|
|
|
|
if self.conversation:
|
|
|
|
|
return self.conversation.conversation_id
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
class AutoReplyConfig(models.Model):
|
|
|
|
|
"""
|
|
|
|
|
自动回复配置模型 - 设置特定用户Gmail与达人Gmail的自动回复规则
|
|
|
|
|
"""
|
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
|
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='auto_reply_configs')
|
|
|
|
|
user_email = models.EmailField(help_text="用户Gmail邮箱")
|
|
|
|
|
influencer_email = models.EmailField(help_text="达人Gmail邮箱")
|
|
|
|
|
is_enabled = models.BooleanField(default=True, help_text="是否启用自动回复")
|
|
|
|
|
goal_description = models.TextField(verbose_name='自动回复的目标描述', help_text="AI回复时参考的目标")
|
|
|
|
|
reply_template = models.TextField(blank=True, null=True, help_text="回复模板(可选,为空则由AI生成)")
|
|
|
|
|
max_replies = models.IntegerField(default=5, help_text="最大自动回复次数")
|
|
|
|
|
current_replies = models.IntegerField(default=0, help_text="当前已自动回复次数")
|
|
|
|
|
last_reply_time = models.DateTimeField(null=True, blank=True)
|
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
|
metadata = models.JSONField(default=dict, blank=True, null=True, help_text="存储额外信息,如已处理的消息ID等")
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
db_table = 'gmail_auto_reply_configs'
|
|
|
|
|
verbose_name = 'Gmail自动回复配置'
|
|
|
|
|
verbose_name_plural = 'Gmail自动回复配置'
|
|
|
|
|
unique_together = ('user', 'user_email', 'influencer_email')
|
|
|
|
|
ordering = ['-updated_at']
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return f"{self.user.username}: {self.user_email} ➔ {self.influencer_email}"
|
|
|
|
|
|
|
|
|
|
def can_reply(self):
|
|
|
|
|
"""检查是否可以继续自动回复"""
|
|
|
|
|
return self.is_enabled and self.current_replies < self.max_replies
|
|
|
|
|
|
|
|
|
|
def increment_reply_count(self):
|
|
|
|
|
"""增加回复计数并更新时间"""
|
|
|
|
|
self.current_replies += 1
|
|
|
|
|
self.last_reply_time = timezone.now()
|
2025-05-20 18:01:02 +08:00
|
|
|
|
self.save(update_fields=['current_replies', 'last_reply_time', 'updated_at'])
|
|
|
|
|
|
|
|
|
|
class ProcessedPushNotification(models.Model):
|
|
|
|
|
"""
|
|
|
|
|
已处理的推送通知记录,用于去重防止重复处理同一个通知
|
|
|
|
|
"""
|
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
|
|
|
message_id = models.CharField(max_length=255, unique=True, help_text="Pub/Sub消息ID")
|
|
|
|
|
email_address = models.EmailField(help_text="通知关联的Gmail邮箱")
|
|
|
|
|
history_id = models.CharField(max_length=100, help_text="Gmail历史ID")
|
|
|
|
|
processed_at = models.DateTimeField(auto_now_add=True, help_text="处理时间")
|
|
|
|
|
is_successful = models.BooleanField(default=True, help_text="处理是否成功")
|
|
|
|
|
metadata = models.JSONField(default=dict, blank=True, help_text="额外信息")
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
db_table = 'gmail_processed_push_notifications'
|
|
|
|
|
verbose_name = '已处理推送通知'
|
|
|
|
|
verbose_name_plural = '已处理推送通知'
|
|
|
|
|
ordering = ['-processed_at']
|
|
|
|
|
indexes = [
|
|
|
|
|
models.Index(fields=['message_id']),
|
|
|
|
|
models.Index(fields=['email_address', 'history_id']),
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return f"{self.email_address} - {self.history_id} ({self.message_id})"
|
|
|
|
|
|
|
|
|
|
class UnmatchedEmail(models.Model):
|
|
|
|
|
"""
|
|
|
|
|
记录未匹配到对话的邮件,避免重复处理
|
|
|
|
|
"""
|
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
|
|
|
message_id = models.CharField(max_length=255, unique=True, help_text="Gmail邮件ID")
|
|
|
|
|
user_id = models.CharField(max_length=36, help_text="用户ID (UUID字符串形式)")
|
|
|
|
|
user_email = models.EmailField(help_text="用户Gmail邮箱")
|
|
|
|
|
from_email = models.EmailField(help_text="发件人邮箱")
|
|
|
|
|
to_email = models.EmailField(help_text="收件人邮箱")
|
|
|
|
|
subject = models.CharField(max_length=500, blank=True, help_text="邮件主题")
|
|
|
|
|
processed_at = models.DateTimeField(auto_now_add=True, help_text="处理时间")
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
db_table = 'gmail_unmatched_emails'
|
|
|
|
|
verbose_name = '未匹配邮件'
|
|
|
|
|
verbose_name_plural = '未匹配邮件'
|
|
|
|
|
ordering = ['-processed_at']
|
|
|
|
|
indexes = [
|
|
|
|
|
models.Index(fields=['message_id']),
|
|
|
|
|
models.Index(fields=['user_id']),
|
|
|
|
|
models.Index(fields=['user_email', 'from_email']),
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return f"{self.user_email} - {self.from_email} - {self.subject}"
|