[{"data":1,"prerenderedAt":1025},["ShallowReactive",2],{"index":3,"posts":34},{"id":4,"title":5,"description":6,"extension":7,"hero":8,"meta":10,"projects":11,"stem":32,"__hash__":33},"index\u002Findex.yml","Nir Galon's website","Nir Galon's portfolio and blog.","yml",{"title":5,"description":9},"Nuxt UI Pro is a collection of premium Vue components built on top of Nuxt UI to create beautiful & responsive Nuxt applications in minutes. It includes all primitives to build landing pages, documentations, blogs, dashboards or entire SaaS products.",{},[12,20,26],{"title":13,"description":14,"to":15,"target":16,"icon":17,"img":18,"class":19},"Subwatch","A chrome extension + desktop app to monitor all your subscriptions in one place. Get notified before renewals, automatically keeps track on your usages to maximize savings, and add subscriptions with only one click right from your Gmail to minimize hassles.","https:\u002F\u002Fsubwatch.co","_blank","i-heroicons-arrow-path-rounded-square","\u002Fprojects\u002Fsubwatch.jpeg","lg:col-span-3",{"title":21,"description":22,"to":23,"target":16,"icon":24,"img":25,"class":19},"Convertics","Radically new analytics. Radically better decisions.","https:\u002F\u002Fconvertics.ai","i-heroicons-square-3-stack-3d","\u002Fprojects\u002Fconvertics.png",{"title":27,"description":28,"to":29,"target":16,"icon":30,"img":31,"class":19},"LearnGit","An interactive course to understand and leverage two of the most indispensable tools in modern software development: git and GitHub.","https:\u002F\u002Flearngit.dev","i-simple-icons-git","\u002Fprojects\u002Flearngit.png","index","Yeb4imtHoq4TS4PKrNj6HwrX3KstlGQTdPHurkJKiQY",[35,225,812],{"id":36,"title":37,"author":38,"badge":43,"body":45,"date":213,"description":214,"draft":215,"extension":216,"image":217,"meta":219,"navigation":220,"path":221,"seo":222,"stem":223,"__hash__":224},"posts\u002Fblog\u002F2025\u002Fsubwatch.md","I built a thing",{"name":39,"to":40,"avatar":41},"Nir Galon","https:\u002F\u002Fx.com\u002Fnirgn975",{"src":42},"\u002Favatar.webp",{"label":44},"solopreneur",{"type":46,"value":47,"toc":209},"minimark",[48,52,55,58,61,71,74,77,135,150,157,160,163,170,173,192,195,201],[49,50,51],"p",{},"I was minding my own business and scrolling X (formally Twitter) when I encountered a post from a designer that put subscriptions logos on top of a calendar month. The UI\u002FUX was stunning! People were loving it and the post got a lot of likes, replies and basically a lot of engagement.",[49,53,54],{},"Right then and there I decided to do a thing - build it. I limit myself to 1 month to release an MVP and see if people are liking it and decided that the pricing model should be a “pay once own it for life”, because it’s kinda funny to pay subscription to monitor other subscriptions.",[49,56,57],{},"The technology I chose to do it is a Chrome Extension, because I can store your data locally and don’t need a server (helps me with the pricing model of paying once) and I can inject a button to your Gmail and get the subscriptions data from the receipt email (it's also good timing).",[49,59,60],{},"I was off to the races, and it’ll not be an understatement to say that I underestimated the amount of work I thought would be required. Nonetheless I finished the Extension and the website a few days before my deadline and I was thrilled!",[49,62,63],{},[64,65],"img",{"alt":66,"className":67,"src":70},"Calendar view",[68,69],"rounded-lg","mx-auto","\u002Fposts\u002F2025\u002Fsubwatch\u002Fdemo-1.jpg",[49,72,73],{},"This is my first product that I’m launching, charging for it, and actively searching for customers. I was a true indie hacker \u002F solopreneur now. But how do I get customers? How do I tell the world about it?",[49,75,76],{},"I got back to X and started asking people, joined indie hackers communities, started to follow some solopreneurs and read their posts, and then I realized that distribution is really hard, maybe even harder than building the product.",[49,78,79,80,87,88,87,93,87,98,87,103,87,108,87,113,87,118,87,123,128,129,134],{},"I started writing replies to posts about related topics and insert my extension if it’s appropriate. I listed my extension on ",[81,82,86],"a",{"href":83,"rel":84},"https:\u002F\u002Fwww.producthunt.com\u002Fproducts\u002Fsubwatch-3",[85],"nofollow","product hunt",", ",[81,89,92],{"href":90,"rel":91},"https:\u002F\u002Fbetalist.com\u002Fstartups\u002Fsubwatch",[85],"betalist",[81,94,97],{"href":95,"rel":96},"https:\u002F\u002Fwww.chromehunt.app\u002Fextensions\u002Fsubwatch",[85],"chromehunt",[81,99,102],{"href":100,"rel":101},"https:\u002F\u002Fmicrolaunch.net\u002Fp\u002Fsubwatch-fd4fd5",[85],"microlaunch",[81,104,107],{"href":105,"rel":106},"https:\u002F\u002Fwww.uneed.best",[85],"uneed",[81,109,112],{"href":110,"rel":111},"https:\u002F\u002Fdealhunt.cc",[85],"dealhunt",[81,114,117],{"href":115,"rel":116},"https:\u002F\u002Fpitchwall.co",[85],"pitchwall",[81,119,122],{"href":120,"rel":121},"https:\u002F\u002Fwww.tinylaun.ch",[85],"tinylaun.ch",[81,124,127],{"href":125,"rel":126},"https:\u002F\u002Fwww.launchingnext.com",[85],"launchingnext",", and ",[81,130,133],{"href":131,"rel":132},"https:\u002F\u002Fwww.saashub.com\u002Fsubwatch-co-alternatives",[85],"saashub"," And I got a few sales 🥳",[49,136,137,138,143,144,149],{},"I also added the website to other platforms, to get backlinks. Like ",[81,139,142],{"href":140,"rel":141},"https:\u002F\u002Fland-book.com\u002Fwebsites\u002F74241-subwatch-subwatch-website",[85],"land book"," and ",[81,145,148],{"href":146,"rel":147},"https:\u002F\u002Fdribbble.com\u002Fshots\u002F25671033-Subwatch-Turn-subscription-chaos-into-financial-harmony",[85],"dribbble",".",[49,151,152],{},[64,153],{"alt":154,"className":155,"src":156},"Insights view",[68,69],"\u002Fposts\u002F2025\u002Fsubwatch\u002Fdemo-2.jpg",[49,158,159],{},"The sales were the fuel to keep me going. Every email notification about a new sale was a celebration. I can’t describe the feeling of someone paying for my product. But there was one thing that bugged me: I was out of ideas about features and updates. Sure, I caught a few bugs and fixed them, but what’s next?",[49,161,162],{},"And then the ideas started to come to me, from paying users, in the form of feature requests. I was so happy I got to work immediately and hammered most of them in a week! Even updated the website to reflect some of the new features.",[49,164,165],{},[64,166],{"alt":167,"className":168,"src":169},"Alerts",[68,69],"\u002Fposts\u002F2025\u002Fsubwatch\u002Fdemo-3.jpg",[49,171,172],{},"Where am I going from here?",[174,175,176,180,183,186,189],"ul",{},[177,178,179],"li",{},"Keep finding free directories and launch my extension in them.",[177,181,182],{},"Working on the website SEO.",[177,184,185],{},"Find Subreddit and Facebook groups to post in (I’m mainly focused on X in my personal life so most of the traffic comes from there at the moment).",[177,187,188],{},"Will add a 7 days trial.",[177,190,191],{},"And I have one other big idea for the product that I’m considering implementing but not sure yet.",[49,193,194],{},"To all of my readers that want to support and buy it, here is a 10% discount code for the first 100 users: BLOG10",[49,196,197,198],{},"The link to the product: ",[81,199,15],{"href":15,"rel":200},[85],[49,202,203,204],{},"The link to the chrome webstore: ",[81,205,208],{"href":206,"rel":207},"https:\u002F\u002Fchromewebstore.google.com\u002Fdetail\u002Fsubwatch\u002Ffnbokglphlnngocknlhgbjcpgjjboeie",[85],"chrome webstore",{"title":210,"searchDepth":211,"depth":211,"links":212},"",2,[],"2025-02-21","Building and launching Subwatch, a pay-once Chrome extension for subscriptions.",false,"md",{"src":218},"\u002Fposts\u002F2025\u002Fsubwatch\u002Fcover.jpeg",{},true,"\u002Fblog\u002F2025\u002Fsubwatch",{"title":37,"description":214},"blog\u002F2025\u002Fsubwatch","KVnqGZb_A9N5HJAXhHiIS_62Gv52Tw_3SJ0eBG3ufTI",{"id":226,"title":227,"author":228,"badge":230,"body":232,"date":803,"description":804,"draft":215,"extension":216,"image":805,"meta":807,"navigation":220,"path":808,"seo":809,"stem":810,"__hash__":811},"posts\u002Fblog\u002F2024\u002Fsupabase-functions.md","Supabase functions",{"name":39,"to":40,"avatar":229},{"src":42},{"label":231},"development",{"type":46,"value":233,"toc":797},[234,248,257,260,263,458,479,484,490,543,548,581,593,600,604,621,700,715,741,745,765,786,790,793],[49,235,236,237,242,243,149],{},"Recently I started to work with ",[81,238,241],{"href":239,"rel":240},"https:\u002F\u002Fsupabase.com",[85],"Supabase",", which I must admit is a joy to use!, but I was stuck on some logic I wanted to do which is to create an organization and a user profile when a new user signs up. So naturally I started to read about supabase functions which are ",[81,244,247],{"href":245,"rel":246},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Ffunctions.html",[85],"PostgreSQL functions",[49,249,250,251,256],{},"It wasn't a very easy thing to do, since I hadn't used ",[81,252,255],{"href":253,"rel":254},"https:\u002F\u002Fwww.postgresql.org",[85],"PostgreSQL"," since version 9 and I didn't know about all the latest and greatest, but that didn't stop me from Google searching it, asking ChatGPT about it, watching some YouTube tutorials, and reading some blog posts on it - I couldn't find anything close to what I needed to achieve. So I put on some old school music and got to work - played with it for hours while reading the PostgreSQL documentation, and finally I succeeded!",[49,258,259],{},"As I said before, I couldn't find anyone that published something close to it, so I figured I have to.",[49,261,262],{},"The signup method looks like this:",[264,265,270],"pre",{"className":266,"code":267,"filename":268,"language":269,"meta":210,"style":210},"language-javascript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const { data, error } = await supabase.auth.signUp({\n  email: localState.email,\n  password: localState.password,\n  options: {\n    data: {\n      first_name: localState.firstName,\n      last_name: localState.lastName,\n      organization_name: localState.organizationName,\n    },\n  },\n});\n","AppRegister.vue","javascript",[271,272,273,326,346,363,374,384,401,418,435,441,447],"code",{"__ignoreMap":210},[274,275,278,282,286,290,293,296,299,302,306,309,311,314,316,320,323],"span",{"class":276,"line":277},"line",1,[274,279,281],{"class":280},"spNyl","const",[274,283,285],{"class":284},"sMK4o"," {",[274,287,289],{"class":288},"sTEyZ"," data",[274,291,292],{"class":284},",",[274,294,295],{"class":288}," error ",[274,297,298],{"class":284},"}",[274,300,301],{"class":284}," =",[274,303,305],{"class":304},"s7zQu"," await",[274,307,308],{"class":288}," supabase",[274,310,149],{"class":284},[274,312,313],{"class":288},"auth",[274,315,149],{"class":284},[274,317,319],{"class":318},"s2Zo4","signUp",[274,321,322],{"class":288},"(",[274,324,325],{"class":284},"{\n",[274,327,328,332,335,338,340,343],{"class":276,"line":211},[274,329,331],{"class":330},"swJcz","  email",[274,333,334],{"class":284},":",[274,336,337],{"class":288}," localState",[274,339,149],{"class":284},[274,341,342],{"class":288},"email",[274,344,345],{"class":284},",\n",[274,347,349,352,354,356,358,361],{"class":276,"line":348},3,[274,350,351],{"class":330},"  password",[274,353,334],{"class":284},[274,355,337],{"class":288},[274,357,149],{"class":284},[274,359,360],{"class":288},"password",[274,362,345],{"class":284},[274,364,366,369,371],{"class":276,"line":365},4,[274,367,368],{"class":330},"  options",[274,370,334],{"class":284},[274,372,373],{"class":284}," {\n",[274,375,377,380,382],{"class":276,"line":376},5,[274,378,379],{"class":330},"    data",[274,381,334],{"class":284},[274,383,373],{"class":284},[274,385,387,390,392,394,396,399],{"class":276,"line":386},6,[274,388,389],{"class":330},"      first_name",[274,391,334],{"class":284},[274,393,337],{"class":288},[274,395,149],{"class":284},[274,397,398],{"class":288},"firstName",[274,400,345],{"class":284},[274,402,404,407,409,411,413,416],{"class":276,"line":403},7,[274,405,406],{"class":330},"      last_name",[274,408,334],{"class":284},[274,410,337],{"class":288},[274,412,149],{"class":284},[274,414,415],{"class":288},"lastName",[274,417,345],{"class":284},[274,419,421,424,426,428,430,433],{"class":276,"line":420},8,[274,422,423],{"class":330},"      organization_name",[274,425,334],{"class":284},[274,427,337],{"class":288},[274,429,149],{"class":284},[274,431,432],{"class":288},"organizationName",[274,434,345],{"class":284},[274,436,438],{"class":276,"line":437},9,[274,439,440],{"class":284},"    },\n",[274,442,444],{"class":276,"line":443},10,[274,445,446],{"class":284},"  },\n",[274,448,450,452,455],{"class":276,"line":449},11,[274,451,298],{"class":284},[274,453,454],{"class":288},")",[274,456,457],{"class":284},";\n",[49,459,460,461,143,464,467,468,471,472,467,475,478],{},"As you can see, there is the email and password, which is passed to the signUp method, but the extra information is ",[271,462,463],{},"first_name",[271,465,466],{},"last_name"," which I wanted to put in the ",[271,469,470],{},"profiles"," table, and the ",[271,473,474],{},"organization_name",[271,476,477],{},"organizations"," table. And, of course, linked them all together with some old school foreign keys.",[480,481,483],"h2",{"id":482},"_1-tables","1. Tables",[49,485,486,487,489],{},"So, first let's create the ",[271,488,470],{}," table:",[264,491,496],{"className":492,"code":493,"filename":494,"language":495,"meta":210,"style":210},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","create table profiles (\n  id uuid references auth.users on delete cascade not null primary key,\n  created_at timestamp with time zone,\n  updated_at timestamp with time zone,\n  first_name text,\n  last_name text,\n  phone text,\n  organization_id uuid references public.organizations on delete cascade not null,\n);\n","createProfiles.sql","sql",[271,497,498,503,508,513,518,523,528,533,538],{"__ignoreMap":210},[274,499,500],{"class":276,"line":277},[274,501,502],{},"create table profiles (\n",[274,504,505],{"class":276,"line":211},[274,506,507],{},"  id uuid references auth.users on delete cascade not null primary key,\n",[274,509,510],{"class":276,"line":348},[274,511,512],{},"  created_at timestamp with time zone,\n",[274,514,515],{"class":276,"line":365},[274,516,517],{},"  updated_at timestamp with time zone,\n",[274,519,520],{"class":276,"line":376},[274,521,522],{},"  first_name text,\n",[274,524,525],{"class":276,"line":386},[274,526,527],{},"  last_name text,\n",[274,529,530],{"class":276,"line":403},[274,531,532],{},"  phone text,\n",[274,534,535],{"class":276,"line":420},[274,536,537],{},"  organization_id uuid references public.organizations on delete cascade not null,\n",[274,539,540],{"class":276,"line":437},[274,541,542],{},");\n",[49,544,545,546,489],{},"And now let's create the ",[271,547,477],{},[264,549,552],{"className":492,"code":550,"filename":551,"language":495,"meta":210,"style":210},"create table organizations (\n  id uuid not null primary key,\n  created_at timestamp with time zone,\n  updated_at timestamp with time zone,\n  name text,\n);\n","createOrganizations.sql",[271,553,554,559,564,568,572,577],{"__ignoreMap":210},[274,555,556],{"class":276,"line":277},[274,557,558],{},"create table organizations (\n",[274,560,561],{"class":276,"line":211},[274,562,563],{},"  id uuid not null primary key,\n",[274,565,566],{"class":276,"line":348},[274,567,512],{},[274,569,570],{"class":276,"line":365},[274,571,517],{},[274,573,574],{"class":276,"line":376},[274,575,576],{},"  name text,\n",[274,578,579],{"class":276,"line":386},[274,580,542],{},[49,582,583,584,586,587,589,590,592],{},"As you can see the ",[271,585,470],{}," table is the one linking between the user from ",[271,588,313],{}," table and the organization from ",[271,591,477],{}," table. And you can head over to the Database tab to see the schema in a graphical representation with Supabase Schema Visualizer:",[49,594,595],{},[64,596],{"alt":597,"className":598,"src":599},"Schema Visualizer",[68,69],"\u002Fposts\u002F2024\u002Fsupabase-functions\u002Fschema-visualizer.webp",[480,601,603],{"id":602},"_2-function","2. Function",[49,605,606,607,609,610,612,613,615,616,143,618,620],{},"Now it's time to create the function itself. The function should get the ",[271,608,474],{}," and create a new organization in the ",[271,611,477],{}," table. We should save the organization's newly created id to then use it when we create a new profile in the ",[271,614,470],{}," table, along with the ",[271,617,463],{},[271,619,466],{}," from the signUp extra options.",[264,622,625],{"className":492,"code":623,"filename":624,"language":495,"meta":210,"style":210},"create or replace function handle_new_org_and_profile() returns trigger as $$\ndeclare\n  org_id uuid;\nbegin\n  -- insert new organization\n  insert into public.organizations(name)\n  values (new.raw_user_meta_data->>'organization_name')\n  returning id into org_id;\n  --  insert new profile\n  insert into public.profiles (id, first_name, last_name, organization_id)\n  values (new.id, new.raw_user_meta_data->>'first_name', new.raw_user_meta_data->>'last_name', org_id);\n  return new;\nend;\n$$ language plpgsql security definer;\n","supabaseTrigger.sql",[271,626,627,632,637,642,647,652,657,662,667,672,677,682,688,694],{"__ignoreMap":210},[274,628,629],{"class":276,"line":277},[274,630,631],{},"create or replace function handle_new_org_and_profile() returns trigger as $$\n",[274,633,634],{"class":276,"line":211},[274,635,636],{},"declare\n",[274,638,639],{"class":276,"line":348},[274,640,641],{},"  org_id uuid;\n",[274,643,644],{"class":276,"line":365},[274,645,646],{},"begin\n",[274,648,649],{"class":276,"line":376},[274,650,651],{},"  -- insert new organization\n",[274,653,654],{"class":276,"line":386},[274,655,656],{},"  insert into public.organizations(name)\n",[274,658,659],{"class":276,"line":403},[274,660,661],{},"  values (new.raw_user_meta_data->>'organization_name')\n",[274,663,664],{"class":276,"line":420},[274,665,666],{},"  returning id into org_id;\n",[274,668,669],{"class":276,"line":437},[274,670,671],{},"  --  insert new profile\n",[274,673,674],{"class":276,"line":443},[274,675,676],{},"  insert into public.profiles (id, first_name, last_name, organization_id)\n",[274,678,679],{"class":276,"line":449},[274,680,681],{},"  values (new.id, new.raw_user_meta_data->>'first_name', new.raw_user_meta_data->>'last_name', org_id);\n",[274,683,685],{"class":276,"line":684},12,[274,686,687],{},"  return new;\n",[274,689,691],{"class":276,"line":690},13,[274,692,693],{},"end;\n",[274,695,697],{"class":276,"line":696},14,[274,698,699],{},"$$ language plpgsql security definer;\n",[49,701,702,703,706,707,710,711,714],{},"The object from the signUp call is hosted in this ",[271,704,705],{},"new"," variable, and the extra options are in the ",[271,708,709],{},"raw_user_meta_data"," field. The two separate insert statements don't share a state, but we need to take the newly created organization id somehow. To do that we ",[271,712,713],{},"declare"," an id at the top of the function which every statement in the function can access too.",[49,716,717,718,721,722,725,726,729,730,733,734,736,737,740],{},"We ",[271,719,720],{},"returning"," the id from the ",[271,723,724],{},"insert"," command ",[271,727,728],{},"into"," the ",[271,731,732],{},"org_id"," variable we declared. Then we can use it in the next ",[271,735,724],{}," statement for our ",[271,738,739],{},"organization_id"," column.",[480,742,744],{"id":743},"_3-trigger","3. Trigger",[49,746,747,748,751,752,757,758,761,762,764],{},"Finally, we somehow need to call this function automatically every time a new row is created in the ",[271,749,750],{},"auth.users"," table, and we can do that with ",[81,753,756],{"href":754,"rel":755},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fdatabase\u002Fpostgres\u002Ftriggers",[85],"triggers",". So let's create a trigger to execute the ",[271,759,760],{},"handle_new_org_and_profile"," function we created above for each row in ",[271,763,750],{}," after the database inserts the row.",[264,766,769],{"className":492,"code":767,"filename":768,"language":495,"meta":210,"style":210},"create trigger on_auth_user_create_org_and_profile\n  after insert on auth.users\n  for each row execute procedure handle_new_org_and_profile();\n","supabseTrigger.sql",[271,770,771,776,781],{"__ignoreMap":210},[274,772,773],{"class":276,"line":277},[274,774,775],{},"create trigger on_auth_user_create_org_and_profile\n",[274,777,778],{"class":276,"line":211},[274,779,780],{},"  after insert on auth.users\n",[274,782,783],{"class":276,"line":348},[274,784,785],{},"  for each row execute procedure handle_new_org_and_profile();\n",[480,787,789],{"id":788},"_4-summary","4. Summary",[49,791,792],{},"You're now all set.",[794,795,796],"style",{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":210,"searchDepth":211,"depth":211,"links":798},[799,800,801,802],{"id":482,"depth":211,"text":483},{"id":602,"depth":211,"text":603},{"id":743,"depth":211,"text":744},{"id":788,"depth":211,"text":789},"2024-02-11","Using a Supabase Postgres trigger to auto-create a profile on signup.",{"src":806},"\u002Fposts\u002F2024\u002Fsupabase-functions\u002Fcover.webp",{},"\u002Fblog\u002F2024\u002Fsupabase-functions",{"title":227,"description":804},"blog\u002F2024\u002Fsupabase-functions","_nrw-NQpAklXH0zZNqwK7SjUfvEyCpTrUxRFiFqgL98",{"id":813,"title":814,"author":815,"badge":817,"body":819,"date":1016,"description":1017,"draft":215,"extension":216,"image":1018,"meta":1020,"navigation":220,"path":1021,"seo":1022,"stem":1023,"__hash__":1024},"posts\u002Fblog\u002F2022\u002Fgithub-email.md","Change your email in GitHub",{"name":39,"to":40,"avatar":816},{"src":42},{"label":818},"tutorials",{"type":46,"value":820,"toc":1009},[821,824,828,837,840,843,847,861,870,874,877,884,887,906,942,956,960,969,980,987,991,1006],[49,822,823],{},"Once in a lifetime a man changes his email address. Maybe you didn't change your email even once, I completely understand you, but I did. And let me tell you - it's not an easy process!",[480,825,827],{"id":826},"_1-why-did-i-do-it","1. Why did I do it?",[49,829,830,831,836],{},"I have a private domain and I used it with Gmail to have a private email address. But Google doesn't give you the option to set up a private domain because they'll lose a lot of small businesses that use ",[81,832,835],{"href":833,"rel":834},"https:\u002F\u002Fworkspace.google.com",[85],"Google workspace",". So I set up a \"small business\" with the domain and paid Google a few dollars every month.",[49,838,839],{},"But then I got married and set up my wife her own custom email address and added a few more dollars to Google every month. And after couple of years, my first child was born, and I thought to myself - when I set up their email in the future I'll also have to pay for their user every month. And I started to realize that in a few years I'll set myself up to pay Google hundreds of dollars every year for an email address (I also pay a yearly fee for the domain).",[49,841,842],{},"But it's 2022 - an email address is basically free nowadays. It doesn't make sense to me. And then Apple announced iOS 15, and one of the features was \"Custom Email Domain with iCloud Mail\"! And I already subscribed to iCloud+, and use iPhone and Mac, so it was a no brainer!",[480,844,846],{"id":845},"_2-apple","2. Apple",[49,848,849,850,143,855,860],{},"I was a Mac and iPhone user and I used Google services, but iOS 15 and iOS 16 changed this. Apple caught up to most (if not all) the major features in Mail, Calendar, and Drive (they have a few more in Photos, and they're already better in Reminders IMO). But they even took a few more steps ahead with ",[81,851,854],{"href":852,"rel":853},"https:\u002F\u002Fsupport.apple.com\u002Fen-om\u002Fguide\u002Ficloud\u002Fmm9d9012c9e8\u002Ficloud",[85],"Hide my Email",[81,856,859],{"href":857,"rel":858},"https:\u002F\u002Fsupport.apple.com\u002Fen-us\u002FHT212614",[85],"Private Relay",". So it was time to move all of my life (Mail, Calendar, Drive, and Photos) to Apple, and to make this process a little easier for me I started with a fresh email address (so I'll not lose the old account for a little longer).",[49,862,863,864,869],{},"The first thing I did is to go over my ",[81,865,868],{"href":866,"rel":867},"https:\u002F\u002F1password.com",[85],"1Password"," login list and enter to all the services and change their email address (or delete the account if I didn't actually use the service, so it was actually a good time to clean things. You'll be surprised how many services you're registered on over the years).",[480,871,873],{"id":872},"_3-github","3. GitHub",[49,875,876],{},"Everything was going according to plan, but little did I know that when you change your email on GitHub you lose all your commits that were made with the old email - they're no longer counted in your contributions heat map!",[49,878,879],{},[64,880],{"alt":881,"className":882,"src":883},"GitHub Contributions Graph",[68,69],"\u002Fposts\u002F2022\u002Fgithub-email\u002Fgithub-contributions-graph.webp",[49,885,886],{},"So I wrote a command line script to change the commits author email. But before we continue I do have to warn you about a few things",[888,889,890,896,903],"ol",{},[177,891,892,149],{},[893,894,895],"strong",{},"This action changes some of the history commit's fields. Be very careful about how you run it and on which repositories",[177,897,898,899,902],{},"Note that you'll need to have full access to the ",[271,900,901],{},"main"," branch to run this script.",[177,904,905],{},"Be aware that the commits that do change will change their sha, so if you have tags that link to those sha's - they'll no longer link to the right commit (you can change the tag sha by creating it again, and you can set the same date when you create it).",[264,907,912],{"className":908,"code":909,"filename":910,"language":911,"meta":210,"style":210},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","git filter-branch -f --commit-filter 'if [ \"$GIT_AUTHOR_EMAIL\" = \"\u003COLD_EMAIL_ADDRESS>\" ]; then GIT_AUTHOR_EMAIL=\"\u003CNEW_EMAIL_ADDRESS>\"; git commit-tree \"$@\"; fi' HEAD\n","terminal","bash",[271,913,914],{"__ignoreMap":210},[274,915,916,920,924,927,930,933,936,939],{"class":276,"line":277},[274,917,919],{"class":918},"sBMFI","git",[274,921,923],{"class":922},"sfazB"," filter-branch",[274,925,926],{"class":922}," -f",[274,928,929],{"class":922}," --commit-filter",[274,931,932],{"class":284}," '",[274,934,935],{"class":922},"if [ \"$GIT_AUTHOR_EMAIL\" = \"\u003COLD_EMAIL_ADDRESS>\" ]; then GIT_AUTHOR_EMAIL=\"\u003CNEW_EMAIL_ADDRESS>\"; git commit-tree \"$@\"; fi",[274,937,938],{"class":284},"'",[274,940,941],{"class":922}," HEAD\n",[49,943,944,945,143,948,951,952,955],{},"All you have to do is to replace ",[271,946,947],{},"\u003COLD_EMAIL_ADDRESS>",[271,949,950],{},"\u003CNEW_EMAIL_ADDRESS>"," with your old email address and new email address respectively, and run this bash command in every one of the repositories you want to change your email address in (and then ",[271,953,954],{},"git push --force origin main"," to change the commit history in GitHub and not just on your local git).",[480,957,959],{"id":958},"_4-github-private-email-addresses","4. GitHub private email addresses",[49,961,962,963,968],{},"There is one thing I'll recommend GitHub users do to not rely on their email address in their commits, and that's using ",[81,964,967],{"href":965,"rel":966},"https:\u002F\u002Fdocs.github.com\u002Fen\u002Faccount-and-profile\u002Fsetting-up-and-managing-your-personal-account-on-github\u002Fmanaging-email-preferences\u002Fsetting-your-commit-email-address",[85],"GitHub private email address"," (it'll also keep your email private from GitHub emails to others and from people who search your repos commit history to find your email address).",[49,970,971,972,976,977,979],{},"So, GitHub has a feature called ",[973,974,975],"em",{},"\"Keep my email address private\""," where they let you use a noreply email address from GitHub as your commit email address. To use your noreply email address for commits you push from the command line, use that email address when you set your commit email address in Git. To use your noreply address for web-based Git operations just set your commit email address on GitHub and choose ",[973,978,975],{}," in the settings (screenshot below).",[49,981,982],{},[64,983],{"alt":984,"className":985,"src":986},"GitHub Private Email Settings",[68,69],"\u002Fposts\u002F2022\u002Fgithub-email\u002Fgithub-private-email.webp",[480,988,990],{"id":989},"_5-summary","5. Summary",[49,992,993,994,996,997,1000,1001,143,1003,1005],{},"So I would recommend everybody to use GitHub's ",[973,995,975],{}," feature, and if you want to change your old commits email to your new ",[271,998,999],{},"noreply"," email address from GitHub - use the bash command I created (don't forget to replace ",[271,1002,947],{},[271,1004,950],{},").",[794,1007,1008],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":210,"searchDepth":211,"depth":211,"links":1010},[1011,1012,1013,1014,1015],{"id":826,"depth":211,"text":827},{"id":845,"depth":211,"text":846},{"id":872,"depth":211,"text":873},{"id":958,"depth":211,"text":959},{"id":989,"depth":211,"text":990},"2022-10-01","Migrating email addresses without losing your GitHub contribution history.",{"src":1019},"\u002Fposts\u002F2022\u002Fgithub-email\u002Fgoogle-email.webp",{},"\u002Fblog\u002F2022\u002Fgithub-email",{"title":814,"description":1017},"blog\u002F2022\u002Fgithub-email","79DXAX_NyMTumetSgbDHEc_5Z7dB-Zp6F9wcD613Elg",1786001638357]