Skip to content

Commit 8d4c0bd

Browse files
Merge pull request #555 from colts661/ywang_ffq_code_lookup
Added admin FFQ code lookup
2 parents 464736e + 4de73f6 commit 8d4c0bd

File tree

4 files changed

+310
-13
lines changed

4 files changed

+310
-13
lines changed

microsetta_private_api/api/_interested_user.py

+17
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
from microsetta_private_api.exceptions import RepoException
88
from microsetta_private_api.repo.campaign_repo import CampaignRepo
99
from microsetta_private_api.repo.melissa_repo import MelissaRepo
10+
from microsetta_private_api.repo.perk_fulfillment_repo import\
11+
PerkFulfillmentRepo
1012
from microsetta_private_api.tasks import send_email
13+
from microsetta_private_api.admin.admin_impl import validate_admin_access
1114

1215

1316
def create_interested_user(body):
@@ -285,3 +288,17 @@ def _validate_user_match(interested_user, email):
285288
# someone doesn't stumble upon a valid email and/or id.
286289
# if they don't both match, treat as invalid
287290
return interested_user.email == email
291+
292+
293+
def search_ffq_codes_by_email(email, token_info):
294+
validate_admin_access(token_info)
295+
296+
with Transaction() as t:
297+
pfr = PerkFulfillmentRepo(t)
298+
ffq_diag = pfr.get_ffq_codes_by_email(email)
299+
ffq_codes_obj = {
300+
"ffq_codes": ffq_diag
301+
}
302+
if ffq_diag is None:
303+
return jsonify(code=404, message="Email not found"), 404
304+
return jsonify(ffq_codes_obj), 200

microsetta_private_api/api/microsetta_private_api.yaml

+27
Original file line numberDiff line numberDiff line change
@@ -2216,6 +2216,33 @@ paths:
22162216
$ref: '#/components/responses/401Unauthorized'
22172217
'404':
22182218
$ref: '#/components/responses/404NotFound'
2219+
2220+
'/admin/search/ffq_codes/{email}':
2221+
get:
2222+
operationId: microsetta_private_api.api._interested_user.search_ffq_codes_by_email
2223+
tags:
2224+
- Admin
2225+
summary: Retrieve FFQ codes by an email query
2226+
description: Retrieve FFQ codes by an email query
2227+
parameters:
2228+
- in: path
2229+
name: email
2230+
description: user email address to search for
2231+
schema:
2232+
type: string
2233+
example: "[email protected]"
2234+
required: true
2235+
responses:
2236+
'200':
2237+
description: Object containing ffq codes and info related to the email (if any)
2238+
content:
2239+
application/json:
2240+
schema:
2241+
type: object
2242+
'401':
2243+
$ref: '#/components/responses/401Unauthorized'
2244+
'404':
2245+
$ref: '#/components/responses/404NotFound'
22192246

22202247
'/admin/search/interested_users/{email}':
22212248
get:

microsetta_private_api/repo/perk_fulfillment_repo.py

+54
Original file line numberDiff line numberDiff line change
@@ -793,3 +793,57 @@ def check_perk_fulfillment_active(self):
793793
)
794794
row = cur.fetchone()
795795
return row['perk_fulfillment_active']
796+
797+
def get_ffq_codes_by_email(self, email):
798+
email = "%" + email + "%"
799+
with self._transaction.dict_cursor() as cur:
800+
# Note: Use left join to differentiate email not found possibility
801+
cur.execute(
802+
"""
803+
WITH all_codes AS (
804+
SELECT
805+
iu.email,
806+
tr.created AS transaction_created_time,
807+
frc.ffq_registration_code,
808+
frc.registration_code_used
809+
FROM campaign.interested_users AS iu
810+
LEFT JOIN campaign.transaction AS tr
811+
ON iu.interested_user_id = tr.interested_user_id
812+
LEFT JOIN campaign.fundrazr_transaction_perk AS ftp
813+
ON tr.id = ftp.transaction_id
814+
LEFT JOIN campaign.fundrazr_ffq_codes AS ffc
815+
ON ftp.id = ffc.fundrazr_transaction_perk_id
816+
LEFT JOIN campaign.ffq_registration_codes AS frc
817+
ON ffc.ffq_registration_code = frc.ffq_registration_code
818+
WHERE iu.email ILIKE %s
819+
), count_codes AS (
820+
SELECT
821+
ac1.email,
822+
COUNT(ac1.ffq_registration_code) AS num_codes
823+
FROM all_codes AS ac1
824+
GROUP BY ac1.email
825+
)
826+
SELECT DISTINCT
827+
ac.email,
828+
CASE WHEN
829+
cc.num_codes = 0 THEN NULL
830+
ELSE
831+
ac.transaction_created_time
832+
END,
833+
ac.ffq_registration_code,
834+
ac.registration_code_used
835+
FROM all_codes AS ac
836+
LEFT JOIN count_codes AS cc
837+
ON ac.email = cc.email
838+
WHERE
839+
ac.ffq_registration_code IS NOT NULL
840+
OR
841+
cc.num_codes = 0
842+
ORDER BY
843+
ac.email ASC,
844+
ac.registration_code_used DESC
845+
""",
846+
(email,)
847+
)
848+
rows = cur.fetchall()
849+
return [dict(row) for row in rows]

microsetta_private_api/repo/tests/test_perk_fulfillment_repo.py

+212-13
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from microsetta_private_api.model.campaign import (FundRazrPayment, Item,
1313
Shipping)
1414
from microsetta_private_api.model.address import Address
15+
from microsetta_private_api.model.activation_code import ActivationCode
1516
from microsetta_private_api.model.daklapack_order import DaklapackOrder
1617
from microsetta_private_api.repo.account_repo import AccountRepo
1718
from microsetta_private_api.repo.admin_repo import AdminRepo
@@ -315,20 +316,102 @@ def setUp(self, verify_address_result):
315316
res = cur.fetchone()
316317
self.sub_ftp_id = res[0]
317318

319+
# dummy information for ffq code search
320+
# interested users
321+
fake_ffq_emails = ["[email protected]"] * 3 + [
322+
323+
] + ["[email protected]"] * 3
324+
cur.execute(
325+
"INSERT INTO campaign.interested_users " +
326+
"(campaign_id, first_name, last_name, email) VALUES " +
327+
", ".join([
328+
f"('{self.test_campaign_id1}', 'Fir', 'Last', '{email}')"
329+
for email in fake_ffq_emails
330+
]) +
331+
"RETURNING interested_user_id"
332+
)
333+
self.iu_ids = [tup[0] for tup in cur.fetchall()]
334+
335+
# transactions
336+
self.tx_ids = {
337+
f"MT{idx + 1}": iu_id
338+
for idx, iu_id in enumerate(self.iu_ids) if idx < 6
339+
}
340+
self.tx_ids['MT7'] = self.iu_ids[-6] # extra tx for [email protected]
341+
cur.execute(
342+
"INSERT INTO campaign.transaction VALUES " +
343+
", ".join([
344+
f"('{tx_id}', '{iu_id}', 'fundrazr', '4Tqx5', " +
345+
"'2023-01-01', 100, 100, 'usd', 'Fir', 'Last', " +
346+
"'[email protected]', 'paypal', 'coolcool', TRUE)"
347+
for tx_id, iu_id in self.tx_ids.items()
348+
])
349+
)
350+
351+
# fundrazr transaction perks
352+
cur.execute(
353+
"INSERT INTO campaign.fundrazr_transaction_perk " +
354+
"(transaction_id, perk_id, quantity, processed) VALUES " +
355+
", ".join([
356+
f"('{tx_id}', '3QeVd', 1, TRUE)" for tx_id in self.tx_ids
357+
if tx_id not in ['MT3', 'MT6']
358+
]) +
359+
"RETURNING id"
360+
)
361+
self.dummy_ftp_ids = [tup[0] for tup in cur.fetchall()]
362+
cur.execute(
363+
"INSERT INTO campaign.fundrazr_transaction_perk "
364+
"(transaction_id, perk_id, quantity, processed) VALUES "
365+
"('MT5', '3QeW6', 1, TRUE) "
366+
"RETURNING id"
367+
) # extra ftp for [email protected]
368+
self.dummy_ftp_ids.append(cur.fetchone()[0])
369+
370+
# ffq registration codes
371+
self.new_ffq_codes = [
372+
ActivationCode.generate_code()
373+
for _ in range(6)
374+
]
375+
ffq_reg_codes = [ # odd idx: used
376+
(code, 'NULL') if idx % 2 == 0 else (code, "'2023-02-01'")
377+
for idx, code in enumerate(self.new_ffq_codes)
378+
]
379+
cur.execute(
380+
"INSERT INTO campaign.ffq_registration_codes VALUES " +
381+
", ".join([
382+
f"('{code}', {used})" for code, used in ffq_reg_codes
383+
])
384+
)
385+
386+
# fundrazr ffq codes
387+
fundrazr_ffq_codes = {}
388+
for idx, code in enumerate(self.new_ffq_codes):
389+
if idx <= 3:
390+
fundrazr_ffq_codes[code] = self.dummy_ftp_ids[idx]
391+
else: # 2 codes
392+
fundrazr_ffq_codes[code] = self.dummy_ftp_ids[4]
393+
cur.execute(
394+
"INSERT INTO campaign.fundrazr_ffq_codes VALUES " +
395+
", ".join([
396+
f"('{ftp_id}', '{code}')"
397+
for code, ftp_id in fundrazr_ffq_codes.items()
398+
])
399+
)
400+
318401
t.commit()
319402

320403
def tearDown(self):
321404
with Transaction() as t:
322405
cur = t.cursor()
323406
cur.execute(
324-
"DELETE FROM campaign.campaigns_projects "
325-
"WHERE campaign_id = %s",
326-
(self.test_campaign_id1,)
407+
"DELETE FROM campaign.fundrazr_ffq_codes AS ffc "
408+
"WHERE ffc.ffq_registration_code In %s",
409+
(tuple(self.new_ffq_codes), )
327410
)
328411
cur.execute(
329-
"DELETE FROM campaign.campaigns "
330-
"WHERE campaign_id = %s",
331-
(self.test_campaign_id1, )
412+
"DELETE FROM campaign.ffq_registration_codes AS frc "
413+
"WHERE frc.ffq_registration_code IN %s",
414+
(tuple(self.new_ffq_codes), )
332415
)
333416
cur.execute(
334417
"DELETE FROM campaign.fundrazr_transaction_perk "
@@ -338,19 +421,38 @@ def tearDown(self):
338421
self.ffq_ftp_id,
339422
self.kit_ftp_id,
340423
self.two_kit_ftp_id,
341-
self.sub_ftp_id
424+
self.sub_ftp_id,
425+
*self.dummy_ftp_ids
342426
),
343427
)
344428
)
345429
cur.execute(
346430
"DELETE FROM campaign.transaction "
347431
"WHERE id IN %s",
348-
((
349-
FFQ_TRANSACTION_ID,
350-
KIT_TRANSACTION_ID,
351-
TWO_KIT_TRANSACTION_ID,
352-
SUB_TRANSACTION_ID
353-
), )
432+
(
433+
(
434+
FFQ_TRANSACTION_ID,
435+
KIT_TRANSACTION_ID,
436+
TWO_KIT_TRANSACTION_ID,
437+
SUB_TRANSACTION_ID,
438+
*self.tx_ids
439+
),
440+
)
441+
)
442+
cur.execute(
443+
"DELETE FROM campaign.interested_users AS iu "
444+
"WHERE iu.interested_user_id IN %s",
445+
(tuple(self.iu_ids), )
446+
)
447+
cur.execute(
448+
"DELETE FROM campaign.campaigns_projects "
449+
"WHERE campaign_id = %s",
450+
(self.test_campaign_id1,)
451+
)
452+
cur.execute(
453+
"DELETE FROM campaign.campaigns "
454+
"WHERE campaign_id = %s",
455+
(self.test_campaign_id1, )
354456
)
355457
t.commit()
356458

@@ -1174,6 +1276,103 @@ def _count_fundrazr_ffq_codes(self, t):
11741276
res = cur.fetchone()
11751277
return res[0]
11761278

1279+
def test_get_ffq_codes_by_email(self):
1280+
with Transaction() as t:
1281+
pfr = PerkFulfillmentRepo(t)
1282+
1283+
# Test: email not found
1284+
ffq_code = pfr.get_ffq_codes_by_email("[email protected]")
1285+
self.assertEqual(len(ffq_code), 0)
1286+
1287+
# Test: no ffq without transaction
1288+
ffq_code = pfr.get_ffq_codes_by_email("[email protected]") # unique
1289+
self.assertEqual(len(ffq_code), 1)
1290+
self.assertEqual(ffq_code[0]['email'], "[email protected]")
1291+
self.assertIsNone(ffq_code[0]['transaction_created_time'])
1292+
self.assertIsNone(ffq_code[0]['ffq_registration_code'])
1293+
1294+
ffq_code = pfr.get_ffq_codes_by_email("[email protected]") # duplicate
1295+
self.assertEqual(len(ffq_code), 1)
1296+
self.assertEqual(ffq_code[0]['email'], "[email protected]")
1297+
self.assertIsNone(ffq_code[0]['ffq_registration_code'])
1298+
1299+
# Test: no ffq with transaction
1300+
ffq_code = pfr.get_ffq_codes_by_email("[email protected]")
1301+
self.assertEqual(len(ffq_code), 1)
1302+
self.assertEqual(ffq_code[0]['email'], "[email protected]")
1303+
self.assertIsNone(ffq_code[0]['transaction_created_time'])
1304+
self.assertIsNone(ffq_code[0]['ffq_registration_code'])
1305+
1306+
# Test: 1 ffq code
1307+
ffq_code = pfr.get_ffq_codes_by_email("[email protected]")
1308+
self.assertEqual(len(ffq_code), 1)
1309+
self.assertEqual(ffq_code[0]['email'], "[email protected]")
1310+
self.assertEqual(
1311+
ffq_code[0]['transaction_created_time'].strftime('%Y-%m-%d'),
1312+
'2023-01-01'
1313+
)
1314+
self.assertEqual(
1315+
ffq_code[0]['ffq_registration_code'], self.new_ffq_codes[2]
1316+
)
1317+
self.assertIsNone(ffq_code[0]['registration_code_used'])
1318+
1319+
# Test: 3 duplicate emails, 2 codes
1320+
ffq_code = pfr.get_ffq_codes_by_email("[email protected]")
1321+
self.assertEqual(len(ffq_code), 2)
1322+
self.assertTrue(
1323+
ffq_code[0]['email'] == ffq_code[1]['email'] == "[email protected]"
1324+
)
1325+
self.assertEqual(
1326+
ffq_code[0]['transaction_created_time'].strftime('%Y-%m-%d'),
1327+
'2023-01-01'
1328+
)
1329+
got_codes = [
1330+
ffq_code[i]['ffq_registration_code']
1331+
for i in range(2)
1332+
]
1333+
self.assertEqual(set(got_codes), set(self.new_ffq_codes[:2]))
1334+
1335+
# Test: unique email, 3 codes
1336+
ffq_code = pfr.get_ffq_codes_by_email("[email protected]")
1337+
self.assertEqual(len(ffq_code), 3)
1338+
self.assertTrue(
1339+
ffq_code[0]['email'] == ffq_code[2]['email'] == "[email protected]"
1340+
)
1341+
self.assertEqual(
1342+
ffq_code[2]['transaction_created_time'].strftime('%Y-%m-%d'),
1343+
'2023-01-01'
1344+
)
1345+
got_codes = [
1346+
ffq_code[i]['ffq_registration_code']
1347+
for i in range(3)
1348+
]
1349+
self.assertEqual(set(got_codes), set(self.new_ffq_codes[3:]))
1350+
1351+
# Test: match multiple emails
1352+
ffq_code = pfr.get_ffq_codes_by_email("iu")
1353+
self.assertEqual(len(ffq_code), 3)
1354+
self.assertTrue(
1355+
ffq_code[1]['email'] == ffq_code[2]['email'] == "[email protected]"
1356+
)
1357+
self.assertEqual(ffq_code[0]['email'], "[email protected]")
1358+
self.assertEqual(
1359+
ffq_code[1]['transaction_created_time'].strftime('%Y-%m-%d'),
1360+
'2023-01-01'
1361+
)
1362+
got_codes = [
1363+
ffq_code[i]['ffq_registration_code']
1364+
for i in range(3)
1365+
]
1366+
self.assertEqual(set(got_codes), set(self.new_ffq_codes[:3]))
1367+
1368+
ffq_code = pfr.get_ffq_codes_by_email("foo.com")
1369+
self.assertEqual(len(ffq_code), 5)
1370+
got_codes = [
1371+
ffq_code[i]['ffq_registration_code']
1372+
for i in range(5)
1373+
]
1374+
self.assertNotIn(self.new_ffq_codes[2], got_codes)
1375+
11771376

11781377
if __name__ == '__main__':
11791378
unittest.main()

0 commit comments

Comments
 (0)