Quando si utilizza il plugin Gamification, i punti vengono assegnati per gli inviti riscattati.
Questo è facilmente abusabile inviando inviti e riscattandoli con un account duplicato (falso).
Quando un tale account falso viene rimosso dall’amministratore, il comportamento del plugin gamification (e del sistema di inviti) dipende da come è stato inviato l’invito.
Se l’invito era legato a uno specifico indirizzo email e l’utente viene rimosso, allora sia il record invite che i record user_invite vengono eliminati definitivamente (!). La prossima volta che il job Sidekiq UpdateScoresFor* viene eseguito, il plugin gamification dedurrà di nuovo i punti assegnati dal punteggio.
Se l’invito non era legato a uno specifico indirizzo email e l’utente viene rimosso, allora il record invited_users verrà rimosso, ma il record invite rimarrà. Il valore redemption_count non sarà decrementato. Questo ha senso da un punto di vista perché il riscatto dell’invito è avvenuto, anche se l’utente è stato rimosso successivamente. Ma il plugin Gamification utilizza redemption_count per calcolare il punteggio quindi i punti non verranno dedotti.
Durante il debug di questo, ho anche scoperto che il job del punteggio considererà solo gli inviti che hanno meno di 10 giorni. Quindi, se un invito viene riscattato dopo 11 giorni, non verranno assegnati punti. Stavo per dire 'Suppongo che debba guardare updated_at invece di created_at ma quando il conteggio dei riscatti per l’invito viene aumentato, il timestamp updated_at non viene toccato.
Qualcosa del genere funzionerebbe meglio (adeguato al formato della query valutabile - questo è un test per l’esploratore di dati ):
-- [params]
-- date :start_date
-- date :end_date
SELECT
invited_by_id AS user_id,
COUNT(*) AS user_invites,
COUNT(*) * 10 AS invite_score
FROM invited_users iu
JOIN invites i ON i.id = iu.invite_id
JOIN users u ON u.id = iu.user_id
WHERE iu.redeemed_at::date BETWEEN :start_date AND :end_date
AND iu.user_id <> i.invited_by_id
AND u.created_at > iu.redeemed_at
GROUP BY invited_by_id
ORDER BY user_invites DESC
Quando un utente viene eliminato, viene rimosso dalla tabella invited_users, quindi non sarebbe più nel conteggio. Se l’eliminazione avvenisse entro 10 giorni, verrebbe corretta automaticamente, se più lunga sarebbe necessario un aggiornamento manuale del punteggio.
L’utilizzo della data redeemed_at terrebbe conto degli inviti creati più di 10 giorni fa.
AND iu.user_id <> i.invited_by_id escluderebbe anche gli auto-inviti.
L’unione della tabella users e l’aggiunta di AND u.created_at > iu.redeemed_at escluderebbero anche l’invito di utenti esistenti.
Questo sarebbe un buon approccio, tranne per una cosa:
Questo non funziona bene. A volte l’utente viene creato leggermente prima che avvenga il riscatto. Non ho idea del perché. Per lo più decimi di secondo, ma ho trovato anche alcuni decine di secondi.
Testato su un database reale.
select
iu.redeemed_at iu_AS redeemed_at,
u.created_at AS u_created_at,
u.created_at > iu.redeemed_at AS u_created_gt_iu_redeemed
from invited_users iu
left join users u on u.id = iu.user_id
where iu.redeemed_at is not null
order by iu.id desc;
iu_redeemed_at | u_created_at | u_created_gt_iu_redeemed
----------------------------+----------------------------+--------------------------
2023-09-08 00:00:47.557057 | 2023-09-08 00:00:48.376446 | t
2023-08-25 20:09:03.486362 | 2023-08-25 20:09:03.201357 | f
2023-08-15 23:38:32.271709 | 2023-08-15 23:38:33.570299 | t
2023-08-14 10:44:34.19912 | 2023-08-14 10:44:35.429371 | t
2023-08-12 13:41:10.428013 | 2023-08-12 13:41:11.733973 | t
2023-07-31 17:58:13.511289 | 2023-07-31 17:57:50.427111 | f
2023-07-23 00:56:33.455185 | 2023-07-23 00:55:47.999263 | f
2023-07-19 08:42:44.908096 | 2023-07-19 08:42:46.040201 | t
2023-06-30 09:11:38.829692 | 2023-06-30 09:11:39.618586 | t
2023-06-30 08:37:02.322192 | 2023-06-30 08:37:03.133769 | t
2023-06-29 16:24:01.705616 | 2023-06-29 16:24:02.55067 | t
2023-06-29 12:53:33.245688 | 2023-06-29 12:53:34.067159 | t
Trovo questo un po’ troppo “magia nera” dato che non sappiamo cosa stia causando il ritardo.
Sospetto che il ritardo sia causato da una grande coda di sidekiq o qualcosa di simile e, sebbene la maggior parte sia inferiore a un secondo, circa il 3% è nell’intervallo 0:00:01 - 0:15:00 (da un secondo a 15 minuti).
E mezzo punto percentuale è nell’ordine dei giorni, il che sembra il tipo di abuso che stiamo cercando di prevenire. Quindi, sebbene questo sia efficace, farebbe più male che bene a causa della quantità di falsi positivi.