Gamification 플러그인을 사용할 경우, 초대 코드를 사용한 사용자에게 포인트가 지급됩니다.
이는 가짜(복제) 계정을 만들어 초대를 보낸 후 해당 계정으로 코드를 사용함으로써 악용하기 쉽습니다.
관리자가 이러한 가짜 계정을 삭제할 때, Gamification 플러그인(및 초대 시스템)의 동작은 초대가 어떻게 보내졌는지에 따라 달라집니다.
초대가 특정 이메일 주소에 연결되어 있었고 사용자가 삭제된 경우, invite 레코드와 user_invite 레코드가 모두 하드(완전) 삭제됩니다. 이후 Sidekiq의 UpdateScoresFor* 작업이 다음으로 실행될 때, Gamification 플러그인은 이전에 지급된 포인트를 점수에서 차감합니다.
초대가 특정 이메일 주소에 연결되지 않았고 사용자가 삭제된 경우, invited_users 레코드는 삭제되지만 invite 레코드는 유지됩니다. redemption_count 값은 감소하지 않습니다. 이는 사용자가 나중에 삭제되었더라도 초대 사용(redemption)이 실제로 발생했기 때문에 한 가지 관점에서는 타당합니다. 그러나 Gamification 플러그인은 redemption_count를 기반으로 점수를 계산하므로, 포인트는 차감되지 않습니다.
이 문제를 디버깅하는 과정에서, 점수 작업이 10일 이내의 초대만 고려한다는 사실도 발견했습니다. 따라서 초대가 11일 이후에 사용되면 포인트가 전혀 지급되지 않습니다. '초대 사용 시 updated_at이 갱신되지 않으므로 created_at 대신 updated_at을 확인해야 할 것 같다’고 말하려 했으나, 실제로는 초대의 redemption_count가 증가할 때 updated_at 타임스탬프가 업데이트되지 않는 것을 확인했습니다.
이런 방향으로 가는 것이 더 나을까요 (점수화 가능한 쿼리 형식에 맞게 조정했습니다 - 데이터 탐색기를 위한 테스트용 쿼리입니다 ):
-- [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
사용자가 삭제되면 invited_users 테이블에서 해당 기록이 제거되므로, 더 이상 카운트에 포함되지 않습니다. 삭제가 10일 이내에 이루어진 경우 자동으로 교정되지만, 10일 이상 지난 경우 수동으로 점수를 새로고침해야 합니다.
redeemed_at 날짜를 사용하면 10일 전에 생성된 초대 코드도 정확히 계산할 수 있습니다.
AND iu.user_id <> i.invited_by_id 조건은 자기 자신에 대한 초대를 제외합니다.
users 테이블을 조인하고 AND u.created_at > iu.redeemed_at 조건을 추가하면 기존 사용자가 초대를 받은 경우를 제외할 수 있습니다.
이 방법은 잘 작동하지 않습니다. 가끔 사용자는 리디엠션(redeem)이 발생한 시점보다 약간 먼저 생성됩니다. 이유를 모르겠습니다. 대부분 0.1초 단위의 차이이지만, 수 초 단위의 차이도 몇 건 발견했습니다.
실제 데이터베이스에서 테스트했습니다.
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