Supabase Edge Functions T-Shirt Contest #2
Mix.install([
{:req, "~> 0.3.2"}
])
Summary
Supabase made annother announcement on Twitter
It's time for another #SupaFunctionsChallenge! This one is a little harder! Can you solve it?
To enter the draw, make a GET request with your email address, your Twitter handle, your t-shirt size, and the correct answer 👇
https://obuldanrptloktxcffvn.functions.supabase.co/get-tshirt-competition?email=tshirt@supabase.io&twitter=supabase&size=2XL&answer=42
Analysis
- Go to the url to get the error response.
{ "error": "Sorry, that's wrong, please try again! HINT: https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/get-tshirt-competition/index.ts" } - Inspecting the source [https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/get-tshirt-competition/index.ts], we find the answer check is on line #49.
if (answer !== countEmailSegments(email!)) throw new Error( `Sorry, that's wrong, please try again! HINT: https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/get-tshirt-competition/index.ts` ) - Translate the
countEmailSegments()function to Elixirfunction countEmailSegments(email: string): string { const [localPart, domain] = email.split('@') const [hostname, ...countryCodes] = domain.split('.') return `${localPart.length}${hostname.length}${countryCodes.reduce((a, cc) => a + cc.length, '')}` } - We can use the same TypeScript playground from our previous contest entry to brute force an answer at [https://www.typescriptlang.org/play]. This will help us check our work.
- We should be able to get a usable answer from the playground but this seems somewhat straightforward to translate. Let's try to go through it line by line.
- The first line of
countEmailSegmentsisconst [localPart, domain] = email.split('@'). This uses destructuring toString.split()the email by the@into 2 parts, the localPart before the @ and the domain, after the @. Let's try this in the next cell. - First we need to create the
secretfor ourEMAIL_ADDRESS.
email = System.fetch_env!("LB_EMAIL_ADDRESS")
email |> String.split("@")
- We can see that splits into a list of
["user", "domain.com"]. Using good ol' pattern matching, we can also destructure into thelocal_partanddomainvariables. We show the output as a tuple to show we captured the same information in a different data structure.
email = System.fetch_env!("LB_EMAIL_ADDRESS")
[local_part, domain] = email |> String.split("@")
{local_part, domain}
-
Now we handle the next line
const [hostname, ...countryCodes] = domain.split('.'). This also uses destructuring to split thedomainvariable on the.with thehostnameas a single variable andcountryCodesas an array. -
This again translates quite well to pattern matching as
[head | tail]wheretailis the rest of our list. Let's see this in the next code example.
email = System.fetch_env!("LB_EMAIL_ADDRESS")
[local_part, domain] = email |> String.split("@")
[host_name, country_codes] = domain |> String.split(".")
{host_name, country_codes}
- For my address, this returns the
host_nameas my domain name andcomas the country_codes. In this case it's a single string (I'm a little unclear when this becomes an array personally, maybe for subdomains?). - The last line is particularly fun as I don't use
reduceas often as I should if I'm being honest. I take slower approaches that build similar accumulators because my brain works with them a little easier. The code isreturn ${localPart.length}${hostname.length}${countryCodes.reduce((a, cc) => a + cc.length, '')}and I'm missing the string interpolation to make this work with markdown's backticks. - The first parts aren't too difficult,
${localPart.length}gets the length of thelocalPartstring,${hostname.length}gets the length of thehostnamestring and${countryCodes.reduce((a, cc) => a + cc.length, '')reduces the array by concatenating the length of each element in the array. - To bring this all together, with an email like
jeremy@some.example.com,localPartisjeremy,hostnameissome, andcountryCodesis["example", "com"]. 15. For a single country code ofcomorus, we'll likely just fudge this and get the length of just that since I'm honestly too lazy to turn the Elixircountry_codesvariable into a list where I believe the syntax...countryCodesin javascript **always pushes the result into an array**. 16. In fact, I'll just use the playground to verify this shortly. Turns out this is correct as thecountryCodesusing my email is["com"]. We can always fudge this by just adding our expected string as a new list or if you're reading along you probably have the right pattern to match this expectancy. 17. From this knowledge we should be able to work out a complete solution. I'll start with handling just the singlecountry_codein Elixir and figure out how to require this to be a list. 1. Man, I can't believe I fudged the[head | tail]syntax as[head, tail]even after specifying it above! 18. For my email address, we should see the response body"Thanks for playing! 6@10.3 has been added to the draw \o/"`. This correctly parses the string we gave it into the individual lengths for each part. Neat.
Solution
url = "https://obuldanrptloktxcffvn.functions.supabase.co/get-tshirt-competition"
countEmailSegments = fn email ->
[local_part, domain] = email |> String.split("@")
[host_name | country_codes] = domain |> String.split(".")
country_codes_length =
Enum.reduce(country_codes, "", fn code, accumulator ->
code_length = String.length(code) |> to_string()
accumulator <> code_length
end)
"#{String.length(local_part)}#{String.length(host_name)}#{country_codes_length}"
end
email = System.fetch_env!("LB_EMAIL_ADDRESS")
answer = countEmailSegments.(email)
parameters = %{
"email" => email,
"twitter" => System.fetch_env!("LB_TWITTER_HANDLE"),
"size" => System.fetch_env!("LB_TSHIRT_SIZE"),
"answer" => answer
}
Req.get!(url, params: parameters)