list a user's own support tickets, Persian digits in completion notifications, rewrite .env.example to match the code

GET /api/support/tickets returns the caller's last 20 tickets with their
support-reply count, on the read throttle so listing never spends the
30/hour create quota. Before it the app kept ticket ids only in one
browser's localStorage, so a ticket and its answer vanished with the device.

Completion notifications now use Persian digits and drop the
'with 0 stars ... 0 points' sentence when nothing was earned.

.env.example: NUM_PROXIES=2 (ArvanCloud + nginx), KAVENEGAR_OTP_TEMPLATE,
BAZAAR_PISHKHAN_API_SECRET, sandbox off by default; removed variables the
code no longer reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 21:18:12 +03:30
parent 8321dc22c3
commit 93b451d422
7 changed files with 192 additions and 163 deletions

View File

@@ -354,3 +354,41 @@ class SupportCreateGuestThrottleBypassTests(TestCase):
self.assertEqual(BugReport.objects.count(), bug_count_before)
self.assertEqual(Ticket.objects.count(), ticket_count_before)
class TicketListTests(TestCase):
"""GET /api/support/tickets: فقط تیکت‌های خودِ کاربر، تازه‌ترین اول، با شمارِ پاسخِ پشتیبانی."""
def setUp(self):
cache.clear()
self.client = APIClient()
self.user = User.objects.create(device_id='device-ticket-list-1', is_guest=True, name='')
self.other = User.objects.create(device_id='device-ticket-list-2', is_guest=True, name='')
self.auth = {'HTTP_AUTHORIZATION': f'Bearer {issue_access_token(self.user.id)}'}
def test_lists_only_own_tickets_with_reply_count(self):
old = Ticket.objects.create(user_id=self.user.id, subject='قدیمی', category='learning')
TicketMessage.objects.create(ticket=old, sender='user', text='سلام')
TicketMessage.objects.create(ticket=old, sender='support', text='جواب')
new = Ticket.objects.create(user_id=self.user.id, subject='تازه', category='payment')
Ticket.objects.create(user_id=self.other.id, subject='مالِ دیگری', category='other')
response = self.client.get('/api/support/tickets', **self.auth)
self.assertEqual(response.status_code, 200)
tickets = response.json()['tickets']
self.assertEqual([t['ticketId'] for t in tickets], [str(new.pk), str(old.pk)])
self.assertEqual(tickets[1]['supportReplies'], 1)
self.assertEqual(tickets[0]['supportReplies'], 0)
def test_requires_authentication(self):
self.assertIn(self.client.get('/api/support/tickets').status_code, (401, 403))
def test_listing_does_not_spend_the_create_quota(self):
with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'support_create': '1/hour'}):
for _ in range(3):
self.assertEqual(self.client.get('/api/support/tickets', **self.auth).status_code, 200)
response = self.client.post(
'/api/support/tickets', {'subject': 'س', 'category': 'other'}, format='json', **self.auth,
)
self.assertEqual(response.status_code, 200)