実装完了

Backend(Django)
backend/apps/mail/serializers.py

MailEmailListSerializer を新規追加(フロントエンド向けメール一覧用)
feedback_token フィールドを含む(フィードバックリンク表示用)
backend/apps/mail/views.py

MailEmailCreateView → MailEmailView に変更(GET+POST を統合)
GET /api/mail/emails/ : JWT認証でメール履歴取得(最新100件、account/verdict フィルター対応)
POST /api/mail/emails/ : APIキー認証でWindmillからのメール記録(既存動作を維持)
get_permissions() でメソッドごとに認証方法を切替
MailStatsView を新規追加
GET /api/mail/stats/ : 今日の処理件数、LINE通知数、フィードバック待ち、ルール数を返す
backend/apps/mail/urls.py

emails/ → MailEmailView(GET+POST)
stats/ → MailStatsView を追加
Frontend(Next.js)
frontend/src/app/mail/history/page.tsx (新規作成)

メール処理履歴の一覧テーブル
アカウント・LLM判定でフィルタリング可能
LLM判定・フィードバック状態をバッジで表示
フィードバックトークンがあれば「回答」リンクを表示
frontend/src/app/dashboard/page.tsx (再設計)

2カラムのモジュールカード形式に変更
作付け計画カード: 年度セレクタ、集計数値、作物別集計、クイックアクセス
メール通知カード: 今日の処理件数、LINE通知数、フィードバック待ち、ルール数、メール履歴・ルール管理ボタン
This commit is contained in:
Akira
2026-02-22 15:01:50 +09:00
parent 7c40480599
commit 04b1ca1bb9
8 changed files with 504 additions and 121 deletions

View File

@@ -32,6 +32,24 @@ class MailEmailCreateSerializer(serializers.ModelSerializer):
]
class MailEmailListSerializer(serializers.ModelSerializer):
"""フロントエンド向けメール一覧用"""
feedback_token = serializers.SerializerMethodField()
class Meta:
model = MailEmail
fields = [
'id', 'account', 'sender_email', 'sender_domain',
'subject', 'received_at', 'llm_verdict',
'notified_at', 'feedback', 'feedback_at', 'feedback_token',
]
def get_feedback_token(self, obj):
if hasattr(obj, 'notification_token'):
return str(obj.notification_token.token)
return None
class FeedbackDetailSerializer(serializers.ModelSerializer):
"""フィードバックページ表示用"""
class Meta:

View File

@@ -9,7 +9,12 @@ urlpatterns = [
# Windmill向けAPIAPIキー認証
path('sender-rule/', views.SenderRuleView.as_view(), name='mail-sender-rule'),
path('sender-context/', views.SenderContextView.as_view(), name='mail-sender-context'),
path('emails/', views.MailEmailCreateView.as_view(), name='mail-email-create'),
# メール記録POST: APIキー認証履歴取得GET: JWT認証
path('emails/', views.MailEmailView.as_view(), name='mail-emails'),
# ダッシュボード用統計JWT認証
path('stats/', views.MailStatsView.as_view(), name='mail-stats'),
# フィードバック認証不要、UUIDトークン
path('feedback/<uuid:token>/', views.FeedbackView.as_view(), name='mail-feedback'),

View File

@@ -12,6 +12,7 @@ from .models import MailSender, MailEmail, MailNotificationToken
from .serializers import (
MailSenderSerializer,
MailEmailCreateSerializer,
MailEmailListSerializer,
FeedbackDetailSerializer,
)
@@ -107,13 +108,29 @@ class SenderContextView(APIView):
})
class MailEmailCreateView(APIView):
class MailEmailView(APIView):
"""
POST /api/mail/emails/
メールを記録する。llm_verdict == 'important' の場合はトークンも発行する。
GET /api/mail/emails/ メール処理履歴を取得JWT認証
POST /api/mail/emails/ メールを記録するAPIキー認証、Windmill向け
"""
permission_classes = [MailAPIKeyPermission]
authentication_classes = []
def get_permissions(self):
if self.request.method == 'POST':
return [MailAPIKeyPermission()]
return [IsAuthenticated()]
def get(self, request):
qs = MailEmail.objects.select_related('notification_token').order_by('-received_at')
account = request.query_params.get('account')
if account:
qs = qs.filter(account=account)
verdict = request.query_params.get('verdict')
if verdict:
qs = qs.filter(llm_verdict=verdict)
serializer = MailEmailListSerializer(qs[:100], many=True)
return Response(serializer.data)
def post(self, request):
serializer = MailEmailCreateSerializer(data=request.data)
@@ -134,6 +151,31 @@ class MailEmailCreateView(APIView):
return Response(response_data, status=status.HTTP_201_CREATED)
class MailStatsView(APIView):
"""
GET /api/mail/stats/ ダッシュボード用統計
"""
permission_classes = [IsAuthenticated]
def get(self, request):
today = timezone.now().date()
today_processed = MailEmail.objects.filter(received_at__date=today).count()
today_notified = MailEmail.objects.filter(notified_at__date=today).count()
feedback_pending = MailEmail.objects.filter(
llm_verdict='important',
feedback__isnull=True
).count()
total_rules = MailSender.objects.count()
return Response({
'today_processed': today_processed,
'today_notified': today_notified,
'feedback_pending': feedback_pending,
'total_rules': total_rules,
})
# ---------------------------------------------------------------------------
# フィードバックビュー(認証不要)
# ---------------------------------------------------------------------------