diff --git a/packages/api/src/routers/_app.ts b/packages/api/src/routers/_app.ts index cf4b4e089..2dd287a15 100644 --- a/packages/api/src/routers/_app.ts +++ b/packages/api/src/routers/_app.ts @@ -7,6 +7,7 @@ import { tagRouter } from './tag' import { secretShopRouter } from './secretShop' import { sendAccountRouter } from './sendAccount' import { accountRecoveryRouter } from './account-recovery/router' +import { referralsRouter } from './referrals' export const appRouter = createTRPCRouter({ chainAddress: chainAddressRouter, @@ -16,6 +17,7 @@ export const appRouter = createTRPCRouter({ distribution: distributionRouter, secretShop: secretShopRouter, sendAccount: sendAccountRouter, + referrals: referralsRouter, }) export type AppRouter = typeof appRouter diff --git a/packages/api/src/routers/referrals.ts b/packages/api/src/routers/referrals.ts new file mode 100644 index 000000000..4d2435415 --- /dev/null +++ b/packages/api/src/routers/referrals.ts @@ -0,0 +1,44 @@ +import { TRPCError } from '@trpc/server' +import { createTRPCRouter, protectedProcedure } from '../trpc' +import debug from 'debug' +import { supabaseAdmin } from 'app/utils/supabase/admin' +const log = debug('api:routers:referrals') + +export const referralsRouter = createTRPCRouter({ + getReferred: protectedProcedure.query(async ({ ctx }) => { + const { user } = ctx.session + if (!user) { + throw new TRPCError({ + code: 'UNAUTHORIZED', + message: 'Unauthorized', + }) + } + const { data: referred, error } = await supabaseAdmin + .from('referrals') + .select('*') + .eq('referred_id', user.id) + .single() + if (error && error.code !== 'PGRST116') { + log('referrals error', error) + throw new TRPCError({ + code: 'INTERNAL_SERVER_ERROR', + message: error.message, + }) + } + if (!referred) return null + const { data: referrerSendAccount, error: referrerError } = await supabaseAdmin + .from('send_accounts') + .select('*') + .eq('id', referred.referrer_id) + .single() + if (referrerError && referrerError.code !== 'PGRST116') { + log('referrals error', referrerError) + throw new TRPCError({ + code: 'INTERNAL_SERVER_ERROR', + message: referrerError.message, + }) + } + + return { referred, referrerSendAccount } + }), +}) diff --git a/packages/app/features/account/rewards/activity/__snapshots__/screen.test.tsx.snap b/packages/app/features/account/rewards/activity/__snapshots__/screen.test.tsx.snap index b57f8420e..b80e1886d 100644 --- a/packages/app/features/account/rewards/activity/__snapshots__/screen.test.tsx.snap +++ b/packages/app/features/account/rewards/activity/__snapshots__/screen.test.tsx.snap @@ -1,18 +1,61 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ActivityRewardsScreen renders: ActivityRewardsScreen 1`] = ` -[ + + - - - - - - - Unlock -
- Extra Rewards -
- - Register at least 1 Sendtag, maintain the minimum balance, -
- avoid selling, and refer others for a bonus multiplier. -
-
+ />
@@ -175,1182 +122,686 @@ exports[`ActivityRewardsScreen renders: ActivityRewardsScreen 1`] = ` accessibilityRole="header" style={ { - "color": "#FFFFFF", + "color": "white", "fontFamily": "System", - "fontSize": 23, - "fontWeight": "600", - "lineHeight": 27, + "fontSize": 30, + "fontWeight": "700", + "lineHeight": 34, "marginBottom": 0, "marginLeft": 0, "marginRight": 0, "marginTop": 0, - "paddingRight": 7, "textTransform": "none", "userSelect": "auto", } } suppressHighlighting={true} > - July Rewards + Unlock +
+ Extra Rewards - + Register at least 1 Sendtag, maintain the minimum balance, +
+ avoid selling, and refer others for a bonus multiplier. + +
+
+ + + July Rewards + + + + + - - July 2024 - - + - + Your SEND Balance + + - - - - - - - + 0 SEND + + - - Your SEND Balance - - 0 SEND + Min. Balance - - - - - Min. Balance - - - + - + - - - - - - + + + + + - + - Sendtag Registered - - + - - - + - + - - - + } + /> + + - + + - - Perks - - - + Perks + - - Multiplier - - + + + - + } + suppressHighlighting={true} + > + Multiplier + - - Estimated July - - - - - 1 SEND - - - - + /> - , - + + Total July + + + 1 SEND + - - Select Month - - - - - - - - - - - - - - - - July 2024 - - - - - - - + - + - - , -] + + `; diff --git a/packages/app/features/account/rewards/activity/screen.test.tsx b/packages/app/features/account/rewards/activity/screen.test.tsx index 001736317..44c9973ea 100644 --- a/packages/app/features/account/rewards/activity/screen.test.tsx +++ b/packages/app/features/account/rewards/activity/screen.test.tsx @@ -8,8 +8,8 @@ jest.mock('app/utils/distributions', () => ({ { number: 7, chain_id: 845337, - qualification_end: new Date(Date.UTC(2024, 6, 30, 11, 59, 59)), - timezone_adjusted_qualification_end: new Date(Date.UTC(2024, 6, 30, 11, 59, 59)), + qualification_end: new Date(2024, 6, 30, 11, 59, 59), + timezone_adjusted_qualification_end: new Date(2024, 6, 30, 11, 59, 59), distribution_shares: [ { amount: 1, @@ -117,7 +117,7 @@ jest.mock('app/utils/useSendAccountBalances', () => ({ describe('ActivityRewardsScreen', () => { it('renders', async () => { jest.useFakeTimers() - jest.setSystemTime(Date.UTC(2024, 6, 12)) + jest.setSystemTime(Date.UTC(2024, 7, 12)) render( @@ -125,9 +125,9 @@ describe('ActivityRewardsScreen', () => { ) await act(async () => { - jest.advanceTimersByTime(2000) + jest.advanceTimersByTime(5000) }) - expect(screen.getByTestId('SelectDistributionDate')).toBeVisible() + expect(screen.toJSON()).toMatchSnapshot('ActivityRewardsScreen') }) }) diff --git a/packages/app/features/account/screen.tsx b/packages/app/features/account/screen.tsx index b4aac8bbb..d57fc05e7 100644 --- a/packages/app/features/account/screen.tsx +++ b/packages/app/features/account/screen.tsx @@ -20,7 +20,6 @@ import { IconShare, IconBadgeCheck, IconArrowRight, - IconQRFull, IconLeaderboard, } from 'app/components/icons' import { getReferralHref } from 'app/utils/getReferralLink' diff --git a/packages/app/features/account/sendtag/checkout/checkout-form.tsx b/packages/app/features/account/sendtag/checkout/checkout-form.tsx index ee88ee0b4..6953909a9 100644 --- a/packages/app/features/account/sendtag/checkout/checkout-form.tsx +++ b/packages/app/features/account/sendtag/checkout/checkout-form.tsx @@ -44,6 +44,7 @@ import { CheckoutTagSchema } from './CheckoutTagSchema' import { ConfirmButton } from './components/checkout-confirm-button' import { SendTagPricingDialog, SendTagPricingTooltip } from './SendTagPricingDialog' import formatAmount from 'app/utils/formatAmount' +import { api } from 'app/utils/api' export const CheckoutForm = () => { const user = useUser() @@ -56,6 +57,7 @@ export const CheckoutForm = () => { const has5Tags = user?.tags?.length === 5 const media = useMedia() const router = useRouter() + const { data: referred, isLoading: isLoadingReferred } = api.referrals.getReferred.useQuery() async function createSendTag({ name }: z.infer) { if (!user.user) return console.error('No user') @@ -277,9 +279,16 @@ export const CheckoutForm = () => { ) }} - - - + {(() => { + switch (true) { + case isLoadingReferred: + return + case !referred: + return + default: + return null + } + })()} {hasPendingTags && ( @@ -389,8 +398,8 @@ function ReferredBy() { {referrer && ( - - + + @@ -409,8 +418,8 @@ function ReferredBy() { } })()} - - + + )} {referrerError && ( diff --git a/packages/app/features/account/sendtag/checkout/checkout-utils.ts b/packages/app/features/account/sendtag/checkout/checkout-utils.ts index d064a44a7..f54abfa3a 100644 --- a/packages/app/features/account/sendtag/checkout/checkout-utils.ts +++ b/packages/app/features/account/sendtag/checkout/checkout-utils.ts @@ -8,6 +8,7 @@ import { import type { SupabaseClient } from '@supabase/supabase-js' import { queryOptions, useQuery } from '@tanstack/react-query' import { reward, total } from 'app/data/sendtags' +import { api } from 'app/utils/api' import { assert } from 'app/utils/assert' import { useSendAccount } from 'app/utils/send-accounts' import { useSupabase } from 'app/utils/supabase/useSupabase' @@ -45,7 +46,6 @@ export function useReferralCode() { queryFn: () => getCookie(REFERRAL_COOKIE_NAME) || null, }) } - /** * Fetches the referrer profile by referral code or tag. * If the referrer is the same as the profile, returns null. The referrer should also have a send account and sendtag. @@ -182,10 +182,13 @@ export function useSendtagCheckout() { const pendingTags = usePendingTags() ?? [] const amountDue = useMemo(() => total(pendingTags ?? []), [pendingTags]) const { data: referrer } = useReferrer() + const { data: referred } = api.referrals.getReferred.useQuery() const { data: reward } = useReferralReward({ tags: pendingTags }) + const referrerAddress = referred?.referrerSendAccount?.address ?? referrer?.address ?? zeroAddress + const checkoutArgs = useMemo( - () => [amountDue, referrer?.address ?? zeroAddress, reward ?? 0n] as const, - [amountDue, referrer, reward] + () => [amountDue, referrerAddress ?? zeroAddress, reward ?? 0n] as const, + [amountDue, referrerAddress, reward] ) const calls = useMemo( () => [ diff --git a/packages/app/features/leaderboard/__snapshots__/screen.test.tsx.snap b/packages/app/features/leaderboard/__snapshots__/screen.test.tsx.snap index 5d88c3d9a..4695fc2fd 100644 --- a/packages/app/features/leaderboard/__snapshots__/screen.test.tsx.snap +++ b/packages/app/features/leaderboard/__snapshots__/screen.test.tsx.snap @@ -355,45 +355,6 @@ exports[`LeaderboardScreen renders leaderboard screen: LeaderboardScreen 1`] = `
- - -
diff --git a/packages/contracts/script/anvil-add-send-merkle-drop-fixtures.ts b/packages/contracts/script/anvil-add-send-merkle-drop-fixtures.ts index 8b6faf685..fe1b0ee27 100755 --- a/packages/contracts/script/anvil-add-send-merkle-drop-fixtures.ts +++ b/packages/contracts/script/anvil-add-send-merkle-drop-fixtures.ts @@ -84,7 +84,6 @@ void (async function main() { // process.exit(1) // return - // biome-ignore lint/correctness/noUnreachable: still cooking console.log(chalk.blue('Geting distribution merkle root from API')) const { root, total } = await fetch('http://localhost:3050/distributor/merkle', { method: 'POST', diff --git a/packages/playwright/tests/account-sendtag-checkout.onboarded.spec.ts b/packages/playwright/tests/account-sendtag-checkout.onboarded.spec.ts index 807c9fe55..6bb382664 100644 --- a/packages/playwright/tests/account-sendtag-checkout.onboarded.spec.ts +++ b/packages/playwright/tests/account-sendtag-checkout.onboarded.spec.ts @@ -130,6 +130,16 @@ const checkReferralCodeVisibility = async ( await expect(referredBy).toBeVisible() } +const checkReferralCodeHidden = async ( + checkoutPage: CheckoutPage, + referrer: { tags: string[]; referral_code: string } +) => { + const refcode = checkoutPage.page.getByLabel('Referral Code:') + const referredBy = checkoutPage.page.getByText(`/${referrer.tags[0]}`) + await expect(refcode).toBeHidden() + await expect(referredBy).toBeHidden() +} + const verifyCheckoutReceipt = async ( supabase: SupabaseClient, tagsToRegister: string[], @@ -268,7 +278,7 @@ test('can refer a tag', async ({ send_id: myProfile.send_id, }, data: { - tags: tagsToRegister, + tags: [tagsToRegister[0]], }, }) @@ -299,7 +309,7 @@ test('can refer multiple tags in separate transactions', async ({ await confirmTags(checkoutPage, firstTags) await verifyTagsInDatabase(supabase, firstTags) await verifyCheckoutReceipt(supabase, firstTags, referrerSendAccount.address) - await verifyActivityFeed(supabase, { + await expect(supabase).toHaveEventInActivityFeed({ event_name: 'referrals', from_user: { send_id: referrer.send_id, @@ -310,7 +320,7 @@ test('can refer multiple tags in separate transactions', async ({ send_id: myProfile.send_id, }, data: { - tags: firstTags, + tags: [firstTags[0]], }, }) const firstRewardAmount = await verifyReferralReward( @@ -327,7 +337,7 @@ test('can refer multiple tags in separate transactions', async ({ for (const tagName of secondTags) { await addPendingTag(checkoutPage, tagName) } - await checkReferralCodeVisibility(checkoutPage, { + await checkReferralCodeHidden(checkoutPage, { referral_code: referrer.referral_code, tags: referrerTags, }) @@ -339,20 +349,6 @@ test('can refer multiple tags in separate transactions', async ({ await confirmTags(checkoutPage, secondTags) await verifyTagsInDatabase(supabase, [...firstTags, ...secondTags]) await verifyCheckoutReceipt(supabase, secondTags, referrerSendAccount.address) - await verifyActivityFeed(supabase, { - event_name: 'referrals', - from_user: { - send_id: referrer.send_id, - tags: referrerTags, - }, - to_user: { - id: myProfile.id, - send_id: myProfile.send_id, - }, - data: { - tags: secondTags, - }, - }) const secondRewardAmount = await verifyReferralReward( referrerSendAccount.address as `0x${string}`, secondTags, @@ -368,7 +364,7 @@ test('can refer multiple tags in separate transactions', async ({ expect(leaderboardData?.[0]).toBeTruthy() assert(!!leaderboardData?.[0], 'leaderboard data not found') expect(leaderboardData[0].rewards_usdc).toBe((firstRewardAmount + secondRewardAmount).toString()) - expect(leaderboardData[0].referrals).toBe((firstTags.length + secondTags.length).toString()) + expect(leaderboardData[0].referrals).toBe((1).toString()) log('leaderboard', leaderboardData) log('done') diff --git a/supabase/database-generated.types.ts b/supabase/database-generated.types.ts index e7e1194bb..f9746d87a 100644 --- a/supabase/database-generated.types.ts +++ b/supabase/database-generated.types.ts @@ -429,7 +429,7 @@ export type Database = { { foreignKeyName: "referrals_referred_id_fkey" columns: ["referred_id"] - isOneToOne: false + isOneToOne: true referencedRelation: "profiles" referencedColumns: ["id"] }, diff --git a/supabase/migrations/20241025230716_update_referral_logic_in_confirm_sendtag_function.sql b/supabase/migrations/20241025230716_update_referral_logic_in_confirm_sendtag_function.sql new file mode 100644 index 000000000..cb101b4cd --- /dev/null +++ b/supabase/migrations/20241025230716_update_referral_logic_in_confirm_sendtag_function.sql @@ -0,0 +1,110 @@ +SET check_function_bodies = OFF; + +CREATE OR REPLACE FUNCTION public.confirm_tags(tag_names citext[], event_id text, referral_code_input text) + RETURNS void + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path TO 'public' + AS $function$ +DECLARE + tag_owner_ids uuid[]; + distinct_user_ids int; + tag_owner_id uuid; + referrer_id uuid; + _event_id alias FOR $2; +BEGIN + -- Check if the tags exist and fetch their owners. + SELECT + array_agg(user_id) INTO tag_owner_ids + FROM + public.tags + WHERE + name = ANY (tag_names) + AND status = 'pending'::public.tag_status; + -- If any of the tags do not exist or are not in pending status, throw an error. + IF array_length(tag_owner_ids, 1) <> array_length(tag_names, 1) THEN + RAISE EXCEPTION 'One or more tags do not exist or are not in pending status.'; + END IF; + -- Check if all tags belong to the same user + SELECT + count(DISTINCT user_id) INTO distinct_user_ids + FROM + unnest(tag_owner_ids) AS user_id; + IF distinct_user_ids <> 1 THEN + RAISE EXCEPTION 'Tags must belong to the same user.'; + END IF; + -- Fetch single user_id + SELECT DISTINCT + user_id INTO tag_owner_id + FROM + unnest(tag_owner_ids) AS user_id; + IF event_id IS NULL OR event_id = '' THEN + RAISE EXCEPTION 'Receipt event ID is required for paid tags.'; + END IF; + -- Ensure event_id matches the sender + IF ( + SELECT + count(DISTINCT scr.sender) + FROM + public.sendtag_checkout_receipts scr + JOIN send_accounts sa ON decode(substring(sa.address, 3), 'hex') = scr.sender + WHERE + scr.event_id = _event_id AND sa.user_id = tag_owner_id) <> 1 THEN + RAISE EXCEPTION 'Receipt event ID does not match the sender'; + END IF; + -- save receipt event_id + INSERT INTO public.receipts( + event_id, + user_id) + VALUES ( + _event_id, + tag_owner_id); + -- Associate the tags with the onchain event + INSERT INTO public.tag_receipts( + tag_name, + event_id) + SELECT + unnest(tag_names), + event_id; + -- Confirm the tags + UPDATE + public.tags + SET + status = 'confirmed'::public.tag_status + WHERE + name = ANY (tag_names) + AND status = 'pending'::public.tag_status; + -- Create referral code redemption (only if it doesn't exist) + IF referral_code_input IS NOT NULL AND referral_code_input <> '' THEN + SELECT + id INTO referrer_id + FROM + public.profiles + WHERE + referral_code = referral_code_input; + IF referrer_id IS NOT NULL AND referrer_id <> tag_owner_id THEN + -- Referrer cannot be the tag owner. + -- Check if a referral already exists for this user + IF NOT EXISTS ( + SELECT + 1 + FROM + public.referrals + WHERE + referred_id = tag_owner_id) THEN + -- Insert only one referral for the user + INSERT INTO public.referrals( + referrer_id, + referred_id, + tag) + SELECT + referrer_id, + tag_owner_id, + unnest(tag_names) + LIMIT 1; + END IF; + END IF; +END IF; +END; +$function$; + diff --git a/supabase/migrations/20241025231201_keep_only_first_referral.sql b/supabase/migrations/20241025231201_keep_only_first_referral.sql new file mode 100644 index 000000000..3357dcd0b --- /dev/null +++ b/supabase/migrations/20241025231201_keep_only_first_referral.sql @@ -0,0 +1,21 @@ +-- Update existing referrals to keep only the oldest referral for each user +WITH ranked_referrals AS ( + SELECT + r.*, + ROW_NUMBER() OVER (PARTITION BY referred_id ORDER BY t.created_at) AS rn + FROM + public.referrals r + JOIN public.tags t ON r.tag = t.name) +DELETE FROM public.referrals +WHERE id IN ( + SELECT + id + FROM + ranked_referrals + WHERE + rn > 1); + +-- Add UNIQUE constraint on referred_id +ALTER TABLE public.referrals + ADD CONSTRAINT unique_referred_id UNIQUE (referred_id); + diff --git a/supabase/migrations/20241025234650_update_leaderboard_referrals_all_time.sql b/supabase/migrations/20241025234650_update_leaderboard_referrals_all_time.sql new file mode 100644 index 000000000..ad11b05d4 --- /dev/null +++ b/supabase/migrations/20241025234650_update_leaderboard_referrals_all_time.sql @@ -0,0 +1,67 @@ +-- Update the referral leaderboard table with data from the referrals and sendtag_checkout_receipts tables +UPDATE + private.leaderboard_referrals_all_time l +SET + referrals = tmp.referrals, + rewards_usdc = tmp.rewards_usdc, + updated_at = now() +FROM ( + SELECT + p.id AS user_id, + coalesce(count(DISTINCT r.referred_id), 0) AS referrals, + coalesce(sum(scr.reward), 0) AS rewards_usdc + FROM + profiles p + LEFT JOIN send_accounts sa ON p.id = sa.user_id + LEFT JOIN ( + SELECT + referrer, + sum(reward) AS reward + FROM + sendtag_checkout_receipts + GROUP BY + referrer) scr ON decode(substr(sa.address, 3), 'hex') = scr.referrer + LEFT JOIN referrals r ON r.referrer_id = p.id +GROUP BY + p.id +HAVING + count(DISTINCT r.referred_id) > 0 + OR sum(scr.reward) > 0) AS tmp +WHERE + l.user_id = tmp.user_id; + +-- Insert new records for users not already in the leaderboard +INSERT INTO private.leaderboard_referrals_all_time( + user_id, + referrals, + rewards_usdc, + updated_at) +SELECT + p.id AS user_id, + coalesce(count(DISTINCT r.referred_id), 0) AS referrals, + coalesce(sum(scr.reward), 0) AS rewards_usdc, + now() AS updated_at +FROM + profiles p + LEFT JOIN send_accounts sa ON p.id = sa.user_id + LEFT JOIN ( + SELECT + referrer, + sum(reward) AS reward + FROM + sendtag_checkout_receipts + GROUP BY + referrer) scr ON decode(substr(sa.address, 3), 'hex') = scr.referrer + LEFT JOIN referrals r ON r.referrer_id = p.id +WHERE + p.id NOT IN ( + SELECT + user_id + FROM + private.leaderboard_referrals_all_time) +GROUP BY + p.id +HAVING + count(DISTINCT r.referred_id) > 0 + OR sum(scr.reward) > 0; + diff --git a/supabase/tests/tag_referrals_test.sql b/supabase/tests/tag_referrals_test.sql index 9414e510b..35126406c 100644 --- a/supabase/tests/tag_referrals_test.sql +++ b/supabase/tests/tag_referrals_test.sql @@ -1,34 +1,41 @@ -- Tag referrals test BEGIN; - -SELECT plan(7); - +SELECT + plan(9); CREATE EXTENSION "basejump-supabase_test_helpers"; - GRANT USAGE ON SCHEMA tests TO service_role; - GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA tests TO service_role; - -- Creating a test user -SELECT tests.create_supabase_user('bob'); - -SELECT tests.create_supabase_user('alice'); - -INSERT INTO send_accounts (user_id, address, chain_id, init_code) +SELECT + tests.create_supabase_user('bob'); +SELECT + tests.create_supabase_user('alice'); +SELECT + tests.create_supabase_user('bob2'); +INSERT INTO send_accounts( + user_id, + address, + chain_id, + init_code) VALUES ( - tests.get_supabase_uid('bob'), + tests.get_supabase_uid( + 'bob'), '0xb0b0000000000000000000000000000000000000', 1, - '\\x00112233445566778899AABBCCDDEEFF' -), + '\\x00112233445566778899AABBCCDDEEFF'), ( - tests.get_supabase_uid('alice'), + tests.get_supabase_uid( + 'alice'), '0xa71ce00000000000000000000000000000000000', 1, - '\\x00112233445566778899AABBCCDDEEFF' -); - -INSERT INTO sendtag_checkout_receipts ( + '\\x00112233445566778899AABBCCDDEEFF'), +( + tests.get_supabase_uid( + 'bob2'), + '0xb0b2000000000000000000000000000000000000', + 1, + '\\x00112233445566778899AABBCCDDEEFF'); +INSERT INTO sendtag_checkout_receipts( chain_id, log_addr, tx_hash, @@ -42,8 +49,7 @@ INSERT INTO sendtag_checkout_receipts ( sender, amount, referrer, - reward -) + reward) VALUES ( 8453, '\x5afe000000000000000000000000000000000000', @@ -58,8 +64,7 @@ VALUES ( '\xb0b0000000000000000000000000000000000000', 1, '\x0000000000000000000000000000000000000000', - 0 -), + 0), ( -- confirm for sendtag @alice 8453, '\x5afe000000000000000000000000000000000000', @@ -74,8 +79,7 @@ VALUES ( '\xa71ce00000000000000000000000000000000000', 1, '\x0000000000000000000000000000000000000000', - 0 -), + 0), ( -- confirm for sendtag @wonderland 8453, '\x5afe000000000000000000000000000000000000', @@ -90,8 +94,8 @@ VALUES ( '\xa71ce00000000000000000000000000000000000', 1, '\x0000000000000000000000000000000000000000', - 0 -), ( -- confirm for sendtag @whiterabbit + 0), +( -- confirm for sendtag @whiterabbit 8453, '\x5afe000000000000000000000000000000000000', '\x1234567890123456789012345678901234567890123456789012345678901234', @@ -105,174 +109,203 @@ VALUES ( '\xa71ce00000000000000000000000000000000000', 1, '\x0000000000000000000000000000000000000000', - 0 -); - + 0), +( -- confirm for sendtag @redroses + 8453, + '\x5afe000000000000000000000000000000000000', + '\x1234567890123456789012345678901234567890123456789012345678901234', + 'sendtag_checkout_receipts', + 'redroses', + 5, + 0, + 0, + 0, + 1234567890, + '\xa71ce00000000000000000000000000000000000', + 1, + '\x0000000000000000000000000000000000000000', + 0); -- Inserting a tag for test user -INSERT INTO tags (name, user_id) +INSERT INTO tags( + name, + user_id) VALUES ( 'alice', - tests.get_supabase_uid('alice') -); - + tests.get_supabase_uid( + 'alice')); +INSERT INTO tags( + name, + user_id) +VALUES ( + 'redroses', + tests.get_supabase_uid( + 'alice')); -- Confirm tags with the service role -SELECT tests.clear_authentication(); - -SELECT set_config('role', 'service_role', true); - -SELECT confirm_tags( - '{alice}', - ( - SELECT event_id - FROM sendtag_checkout_receipts - WHERE sender = '\xa71ce00000000000000000000000000000000000' AND src_name = 'alice' - ), - ( - SELECT referral_code - FROM public.profiles - WHERE id = tests.get_supabase_uid('bob') - ) -); - +SELECT + tests.clear_authentication(); +SELECT + set_config('role', 'service_role', TRUE); +SELECT + confirm_tags('{alice}',( + SELECT + event_id + FROM sendtag_checkout_receipts + WHERE + sender = '\xa71ce00000000000000000000000000000000000' + AND src_name = 'alice'),( + SELECT + referral_code + FROM public.profiles + WHERE + id = tests.get_supabase_uid('bob'))); -- Verify that the tags were confirmed -SELECT isnt_empty( - $$ - SELECT * - FROM tags - WHERE status = 'confirmed'::tag_status - and user_id = tests.get_supabase_uid('alice') $$, - 'Tags should be confirmed' -); - -SELECT isnt_empty( - $test$ - SELECT tag - FROM referrals - WHERE referrer_id = tests.get_supabase_uid('bob') - and referred_id = tests.get_supabase_uid('alice') $test$, - 'Referral should be created' -); - +SELECT + isnt_empty($$ + SELECT + * FROM tags + WHERE + status = 'confirmed'::tag_status + AND user_id = tests.get_supabase_uid('alice') $$, 'Tags should be confirmed'); +SELECT + isnt_empty($test$ + SELECT + tag FROM referrals + WHERE + referrer_id = tests.get_supabase_uid('bob') + AND referred_id = tests.get_supabase_uid('alice') $test$, 'Referral should be created'); +-- Verify user cannot have two referrers +SELECT + confirm_tags('{redroses}',( + SELECT + event_id + FROM sendtag_checkout_receipts + WHERE + sender = '\xa71ce00000000000000000000000000000000000' + AND src_name = 'redroses'),( + SELECT + referral_code + FROM public.profiles + WHERE + id = tests.get_supabase_uid('bob2'))); +-- Verify that the tags were confirmed +SELECT + isnt_empty($$ + SELECT + * FROM tags + WHERE + status = 'confirmed'::tag_status + AND user_id = tests.get_supabase_uid('alice') $$, 'Tags should be confirmed'); +-- Verify no referral was created +SELECT + is_empty($test$ + SELECT + tag FROM referrals + WHERE + referrer_id = tests.get_supabase_uid('bob2') + AND referred_id = tests.get_supabase_uid('alice') $test$, 'Referral should not be created'); -- Verify user can see referral activity -SELECT tests.authenticate_as('bob'); - -SELECT results_eq( - $$ - SELECT data->>'tags', (from_user).tags, (to_user).tags - FROM activity_feed - WHERE event_name = 'referrals' - $$, - $$ - VALUES ('["alice"]', - null::text[], - '{"alice"}'::text[]) $$, - 'verify referral activity was created' -); - +SELECT + tests.authenticate_as('bob'); +SELECT + results_eq($$ + SELECT + data ->> 'tags',(from_user).tags,(to_user).tags FROM activity_feed + WHERE + event_name = 'referrals' $$, $$ + VALUES ('["alice"]', NULL::text[], '{"alice","redroses"}'::text[]) $$, 'verify referral activity was created'); -- admin deleting referral should delete activity -SELECT tests.clear_authentication(); - -SELECT set_config('role', 'service_role', true); - +SELECT + tests.clear_authentication(); +SELECT + set_config('role', 'service_role', TRUE); DELETE FROM referrals -WHERE - referrer_id = tests.get_supabase_uid('bob') +WHERE referrer_id = tests.get_supabase_uid('bob') AND referred_id = tests.get_supabase_uid('alice'); - -SELECT results_eq( - $$ - SELECT COUNT(*)::integer - FROM activity - WHERE event_name = 'referrals' and event_id = sha256(decode(replace(tests.get_supabase_uid('alice')::text, '-', ''), 'hex'))::text - $$, - $$ - VALUES (0) $$, - 'verify referral activity was deleted' -); - +SELECT + results_eq($$ + SELECT + COUNT(*)::integer FROM activity + WHERE + event_name = 'referrals' + AND event_id = sha256(decode(replace(tests.get_supabase_uid('alice')::text, '-', ''), 'hex'))::text $$, $$ + VALUES (0) $$, 'verify referral activity was deleted'); -- Verify invalid referral code still confirms tags -SELECT tests.authenticate_as('alice'); - -INSERT INTO tags (name, user_id) +SELECT + tests.authenticate_as('alice'); +INSERT INTO tags( + name, + user_id) VALUES ( 'wonderland', - tests.get_supabase_uid('alice') -); - + tests.get_supabase_uid( + 'alice')); -- Confirm tags with the service role -SELECT tests.clear_authentication(); - -SELECT set_config('role', 'service_role', true); - -SELECT confirm_tags( - '{wonderland}', - ( - SELECT event_id - FROM sendtag_checkout_receipts - WHERE sender = '\xa71ce00000000000000000000000000000000000' AND src_name = 'wonderland' - ), - 'invalid' -); - +SELECT + tests.clear_authentication(); +SELECT + set_config('role', 'service_role', TRUE); +SELECT + confirm_tags('{wonderland}',( + SELECT + event_id + FROM sendtag_checkout_receipts + WHERE + sender = '\xa71ce00000000000000000000000000000000000' + AND src_name = 'wonderland'), 'invalid'); -- Verify that the tags were confirmed -SELECT isnt_empty( - $$ - SELECT * - FROM tags - WHERE status = 'confirmed'::tag_status - and user_id = tests.get_supabase_uid('alice') - and name = 'wonderland' $$, - 'Tags should be confirmed' -); - +SELECT + isnt_empty($$ + SELECT + * FROM tags + WHERE + status = 'confirmed'::tag_status + AND user_id = tests.get_supabase_uid('alice') + AND name = 'wonderland' $$, 'Tags should be confirmed'); -- Verify passing my own referral code does not create a referral -SELECT tests.authenticate_as('alice'); - -INSERT INTO tags (name, user_id) +SELECT + tests.authenticate_as('alice'); +INSERT INTO tags( + name, + user_id) VALUES ( 'whiterabbit', - tests.get_supabase_uid('alice') -); - + tests.get_supabase_uid( + 'alice')); -- Confirm tags with the service role -SELECT tests.clear_authentication(); - -SELECT set_config('role', 'service_role', true); - -SELECT confirm_tags( - '{whiterabbit}', - ( - SELECT event_id - FROM sendtag_checkout_receipts - WHERE sender = '\xa71ce00000000000000000000000000000000000' AND src_name = 'whiterabbit' - ), - ( - SELECT referral_code - FROM public.profiles - WHERE id = tests.get_supabase_uid('alice') - ) -); - +SELECT + tests.clear_authentication(); +SELECT + set_config('role', 'service_role', TRUE); +SELECT + confirm_tags('{whiterabbit}',( + SELECT + event_id + FROM sendtag_checkout_receipts + WHERE + sender = '\xa71ce00000000000000000000000000000000000' + AND src_name = 'whiterabbit'),( + SELECT + referral_code + FROM public.profiles + WHERE + id = tests.get_supabase_uid('alice'))); -- Verify that the tags were confirmed -SELECT isnt_empty( - $$ - SELECT * - FROM tags - WHERE status = 'confirmed'::tag_status - and user_id = tests.get_supabase_uid('alice') - and name = 'whiterabbit' $$, - 'Tags should be confirmed' -); - +SELECT + isnt_empty($$ + SELECT + * FROM tags + WHERE + status = 'confirmed'::tag_status + AND user_id = tests.get_supabase_uid('alice') + AND name = 'whiterabbit' $$, 'Tags should be confirmed'); -- Verify no referral was created -SELECT is_empty( - $$ - SELECT * - FROM referrals - WHERE referrer_id = tests.get_supabase_uid('alice') $$, - 'Referral should not be created' -); - -SELECT finish(); - +SELECT + is_empty($$ + SELECT + * FROM referrals + WHERE + referrer_id = tests.get_supabase_uid('alice') $$, 'Referral should not be created'); +SELECT + finish(); ROLLBACK; +