Bei Verwendung des Gamification-Plugins werden Punkte für eingelöste Einladungen vergeben.
Dies wird leicht missbraucht, indem Einladungen verschickt und mit einem doppelten (gefälschten) Konto eingelöst werden.
Wenn ein solches gefälschtes Konto vom Administrator entfernt wird, hängt das Verhalten des Gamification-Plugins (und des Einladungssystems) davon ab, wie die Einladung versendet wurde.
Wenn die Einladung an eine bestimmte E-Mail-Adresse gebunden war und der Benutzer entfernt wird, werden sowohl der invite-Datensatz als auch die user_invite-Datensätze unwiderruflich (!) gelöscht. Wenn das Sidekiq UpdateScoresFor*-Job das nächste Mal ausgeführt wird, wird das Gamification-Plugin die vergebenen Punkte wieder vom Punktestand abziehen.
Wenn die Einladung nicht an eine bestimmte E-Mail-Adresse gebunden war und der Benutzer entfernt wird, wird der invited_users-Datensatz entfernt, aber der invite-Datensatz bleibt erhalten. Der Wert redemption_count wird nicht verringert. Dies ist aus einem Blickwinkel sinnvoll, da die Einlösung der Einladung stattgefunden hat, auch wenn der Benutzer nachträglich entfernt wurde. Aber das Gamification-Plugin verwendet redemption_count zur Berechnung des Punktestands, sodass die Punkte nicht abgezogen werden.
Beim Debuggen dessen habe ich auch festgestellt, dass der Score-Job nur Einladungen berücksichtigt, die weniger als 10 Tage alt sind. Wenn also eine Einladung nach 11 Tagen eingelöst wird, werden überhaupt keine Punkte vergeben. Ich wollte gerade sagen: ‘Ich schätze, es muss updated_at anstelle von created_at betrachtet werden’, aber wenn die Einlösungsanzahl für die Einladung erhöht wird, wird der updated_at-Zeitstempel nicht berührt.
Würde etwas in dieser Art besser funktionieren (angepasst an das scorable query-Format – dies ist ein Test für den Data Explorer):
-- [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
Wenn ein Benutzer gelöscht wird, wird er aus der Tabelle invited_users entfernt und wäre somit nicht mehr in der Zählung enthalten. Wenn die Löschung innerhalb von 10 Tagen erfolgte, würde sie automatisch korrigiert werden, wenn sie länger dauert, müsste eine manuelle Score-Aktualisierung erfolgen.
Die Verwendung des redeemed_at-Datums würde diejenigen Einladungen berücksichtigen, die vor mehr als 10 Tagen erstellt wurden.
AND iu.user_id <> i.invited_by_id würde auch Selbst-Einladungen ausschließen.
Das Hinzufügen der users-Tabelle und die Bedingung AND u.created_at > iu.redeemed_at würden auch das Einladen bestehender Benutzer ausschließen.
Das funktioniert nicht gut. Manchmal wird der Benutzer kurz vor der Einlösung erstellt. Keine Ahnung warum. Meistens Zehntelsekunden, aber ich habe auch ein paar Zehntelsekunden gefunden.
Auf einer echten Datenbank getestet.
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
Das ist interessant. Das hatte ich definitiv nicht berücksichtigt.
Würde das Hinzufügen eines kleinen Puffers wie AND u.created_at + INTERVAL '1 SECOND' > iu.redeemed_at dies kompensieren, ohne es zu sehr zu beeinträchtigen?
Ich finde das ein wenig zu viel „schwarze Magie“, da wir nicht wissen, was die Verzögerung verursacht.
Ich vermute, die Verzögerung wird durch eine große Sidekiq-Warteschlange oder etwas Ähnliches verursacht, und obwohl die Mehrheit unter einer Sekunde liegt, liegen etwa 3 % im Bereich von 0:00:01 - 0:15:00 (eine Sekunde bis 15 Minuten).
Und ein halbes Prozent liegt in der Größenordnung von Tagen, was die Art von Missbrauch zu sein scheint, die wir zu verhindern versuchen. Obwohl dies wirksam ist, würde es aufgrund der vielen Fehlalarme mehr schaden als nützen.