main
Laurent 8 months ago
parent d48a03320e
commit 9ed7e126a5
  1. 40
      subscriptions/templates/subscriptions/base.html
  2. 39
      subscriptions/templates/subscriptions/monthly_summary.html
  3. 106
      subscriptions/templates/subscriptions/offer_detail.html
  4. 110
      subscriptions/views.py

@ -0,0 +1,40 @@
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Poker Analytics{% endblock %}</title>
{% load static %}
<link rel="stylesheet" href="{% static 'tournaments/css/output.css' %}" />
<link rel="stylesheet" href="{% static 'tournaments/css/main.css' %}" />
</head>
<body class="bg-gray-100 min-h-screen">
<header class="bg-blue-600 text-white shadow-md">
<div class="container mx-auto px-4 py-4">
<div class="flex justify-between items-center">
<a href="/" class="text-2xl font-bold">Poker Analytics</a>
<nav>
<ul class="flex space-x-6">
<li><a href="{% url 'subscriptions:monthly_summary' %}" class="hover:text-blue-200">Subscriptions</a></li>
<!-- Add other navigation items here -->
</ul>
</nav>
</div>
</div>
</header>
<main class="container mx-auto px-4 py-8">
{% block content %}{% endblock %}
</main>
<footer class="bg-gray-800 text-white py-6 mt-auto">
<div class="container mx-auto px-4">
<p class="text-center">&copy; {% now "Y" %} Poker Analytics. All rights reserved.</p>
</div>
</footer>
{% block scripts %}{% endblock %}
</body>
</html>

@ -0,0 +1,39 @@
<!-- templates/subscriptions/monthly_summary.html -->
{% extends "base.html" %}
{% block title %}Monthly Offer Summary{% endblock %}
{% block content %}
<div class="max-w-5xl mx-auto">
<h1 class="text-3xl font-bold text-gray-800 mb-6">Monthly Offer Summary</h1>
{% if monthly_offers %}
{% for month_data in monthly_offers %}
<div class="bg-white rounded-lg shadow-md mb-6 overflow-hidden">
<div class="bg-blue-500 text-white px-6 py-3">
<h3 class="text-xl font-semibold">{{ month_data.month|date:"F Y" }}</h3>
</div>
<div class="p-4">
<ul class="divide-y divide-gray-200">
{% for offer in month_data.offers %}
<li class="py-3 flex justify-between items-center">
<a href="{% url 'subscriptions:offer_detail' month_data.month|date:'Y' month_data.month|date:'m' offer.offerIdentifier %}"
class="text-blue-600 hover:text-blue-800 hover:underline">
{{ offer.offerIdentifier }}
</a>
<span class="bg-blue-100 text-blue-800 text-sm font-medium px-3 py-1 rounded-full">
{{ offer.count }}
</span>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endfor %}
{% else %}
<div class="bg-blue-50 border-l-4 border-blue-500 text-blue-700 p-4" role="alert">
<p>No offers available.</p>
</div>
{% endif %}
</div>
{% endblock %}

@ -0,0 +1,106 @@
<!-- templates/subscriptions/offer_detail.html -->
{% extends "base.html" %}
{% block title %}Offer Detail: {{ offer_id }} - {{ month_name }}{% endblock %}
{% block content %}
<div class="max-w-6xl mx-auto">
<div class="flex justify-between items-center mb-6">
<h1 class="text-3xl font-bold text-gray-800">{{ offer_id }} - {{ month_name }}</h1>
<a href="{% url 'subscriptions:monthly_summary' %}"
class="bg-gray-500 hover:bg-gray-600 text-white font-medium py-2 px-4 rounded inline-flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z" clip-rule="evenodd" />
</svg>
Back to Summary
</a>
</div>
{% if notifications %}
<div class="bg-white shadow-md rounded-lg overflow-hidden mb-8">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Signed Date
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Notification Type
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Original Transaction ID
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Offer Identifier
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
{% for notification in notifications %}
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ notification.signedDate }}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{% if notification.notificationType == 'SUBSCRIBED' %}
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
{{ notification.notificationType }}
</span>
{% elif notification.notificationType == 'REFUND' %}
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
{{ notification.notificationType }}
</span>
{% else %}
{{ notification.notificationType }}
{% endif %}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ notification.originalTransactionId }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ notification.offerIdentifier }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="bg-white shadow-md rounded-lg overflow-hidden">
<div class="px-6 py-4 bg-gray-50 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-800">Summary</h3>
</div>
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
<h4 class="text-green-800 text-lg font-medium mb-2">Subscribed</h4>
<p class="text-3xl font-bold text-green-600">{{ subscribed_count }}</p>
</div>
<div class="bg-red-50 rounded-lg p-4 border border-red-200">
<h4 class="text-red-800 text-lg font-medium mb-2">Refunds</h4>
<p class="text-3xl font-bold text-red-600">{{ refund_count }}</p>
</div>
<div class="bg-blue-50 rounded-lg p-4 border border-blue-200">
<h4 class="text-blue-800 text-lg font-medium mb-2">Net Subscriptions</h4>
<p class="text-3xl font-bold text-blue-600">{{ net_count }}</p>
</div>
<div class="bg-purple-50 rounded-lg p-4 border border-purple-200">
<h4 class="text-purple-800 text-lg font-medium mb-2">Revenue ($15/sub)</h4>
<p class="text-3xl font-bold text-purple-600">${{ revenue }}</p>
</div>
</div>
</div>
</div>
{% else %}
<div class="bg-blue-50 border-l-4 border-blue-500 text-blue-700 p-4" role="alert">
<p>No subscription or refund notifications found for this offer in {{ month_name }}.</p>
</div>
{% endif %}
</div>
{% endblock %}

@ -1,8 +1,13 @@
from django.shortcuts import render
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from .models import ASSNotification
from django.views.generic import ListView, DetailView
from django.db.models import Count, Q, Sum, Case, When, IntegerField
from django.conf import settings
from django.db.models.functions import TruncMonth
from django.shortcuts import render
from .models import ASSNotification
from appstoreserverlibrary.signed_data_verifier import VerificationException, SignedDataVerifier
from appstoreserverlibrary.api_client import AppStoreServerAPIClient, APIException
@ -190,3 +195,106 @@ def _get_root_cert(root_cert_path):
root_cert = load_certificate(FILETYPE_ASN1, data)
return root_cert
class MonthlyOfferSummaryView(ListView):
template_name = 'subscriptions/monthly_summary.html'
context_object_name = 'monthly_offers'
def get_queryset(self):
# Annotate notifications with month, then group by month and offerIdentifier
# Only include entries where offerIdentifier is not null
monthly_offers = ASSNotification.objects.exclude(offerIdentifier__isnull=True)\
.annotate(month=TruncMonth('signedDate'))\
.values('month', 'offerIdentifier')\
.annotate(count=Count('id'))\
.order_by('-month', 'offerIdentifier')
# Group by month
result = {}
for offer in monthly_offers:
month = offer['month']
if month not in result:
result[month] = []
result[month].append({
'offerIdentifier': offer['offerIdentifier'],
'count': offer['count']
})
# Convert to list of dicts for template
monthly_data = [
{'month': month, 'offers': offers}
for month, offers in result.items()
]
return sorted(monthly_data, key=lambda x: x['month'], reverse=True)
class OfferDetailView(ListView):
template_name = 'subscriptions/offer_detail.html'
context_object_name = 'notifications'
def get_queryset(self):
month = self.kwargs.get('month')
year = self.kwargs.get('year')
offer_id = self.kwargs.get('offer_id')
# Get the first day of the month and last day of the month
start_date = datetime.date(int(year), int(month), 1)
if int(month) == 12:
end_date = datetime.date(int(year) + 1, 1, 1) - datetime.timedelta(days=1)
else:
end_date = datetime.date(int(year), int(month) + 1, 1) - datetime.timedelta(days=1)
# Filter notifications
return ASSNotification.objects.filter(
signedDate__date__gte=start_date,
signedDate__date__lte=end_date,
offerIdentifier=offer_id,
notificationType__in=['SUBSCRIBED', 'REFUND']
).order_by('signedDate')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
month = self.kwargs.get('month')
year = self.kwargs.get('year')
offer_id = self.kwargs.get('offer_id')
# Get the first day of the month and last day of the month
start_date = datetime.date(int(year), int(month), 1)
if int(month) == 12:
end_date = datetime.date(int(year) + 1, 1, 1) - datetime.timedelta(days=1)
else:
end_date = datetime.date(int(year), int(month) + 1, 1) - datetime.timedelta(days=1)
# Count statistics
stats = ASSNotification.objects.filter(
signedDate__date__gte=start_date,
signedDate__date__lte=end_date,
offerIdentifier=offer_id,
notificationType__in=['SUBSCRIBED', 'REFUND']
).aggregate(
subscribed_count=Count(Case(
When(notificationType='SUBSCRIBED', then=1),
output_field=IntegerField()
)),
refund_count=Count(Case(
When(notificationType='REFUND', then=1),
output_field=IntegerField()
))
)
subscribed_count = stats['subscribed_count'] or 0
refund_count = stats['refund_count'] or 0
net_count = subscribed_count - refund_count
revenue = net_count * 15 # $15 per net subscription
context.update({
'month_name': start_date.strftime('%B %Y'),
'offer_id': offer_id,
'subscribed_count': subscribed_count,
'refund_count': refund_count,
'net_count': net_count,
'revenue': revenue
})
return context

Loading…
Cancel
Save