@@ -1716,6 +1716,107 @@ export const clearImagePreset = async ({
17161716 }
17171717} ;
17181718
1719+ /**
1720+ * Platform URL templates for building URLs from handles
1721+ */
1722+ const PLATFORM_URL_TEMPLATES : Record < string , ( handle : string ) => string > = {
1723+ twitter : ( handle ) => `https://x.com/${ handle } ` ,
1724+ github : ( handle ) => `https://github.com/${ handle } ` ,
1725+ linkedin : ( handle ) => `https://linkedin.com/in/${ handle } ` ,
1726+ threads : ( handle ) => `https://threads.net/@${ handle } ` ,
1727+ roadmap : ( handle ) => `https://roadmap.sh/u/${ handle } ` ,
1728+ codepen : ( handle ) => `https://codepen.io/${ handle } ` ,
1729+ reddit : ( handle ) => `https://reddit.com/u/${ handle } ` ,
1730+ stackoverflow : ( handle ) => `https://stackoverflow.com/users/${ handle } ` ,
1731+ youtube : ( handle ) => `https://youtube.com/@${ handle } ` ,
1732+ bluesky : ( handle ) => `https://bsky.app/profile/${ handle } ` ,
1733+ hashnode : ( handle ) => `https://hashnode.com/@${ handle } ` ,
1734+ // These platforms store full URLs, not handles
1735+ mastodon : ( url ) => url ,
1736+ portfolio : ( url ) => url ,
1737+ } ;
1738+
1739+ /**
1740+ * Build URL from handle for a given platform
1741+ */
1742+ function buildUrlFromHandle (
1743+ handle : string | null | undefined ,
1744+ platform : string ,
1745+ ) : string | null {
1746+ if ( ! handle ) return null ;
1747+ const template = PLATFORM_URL_TEMPLATES [ platform ] ;
1748+ if ( ! template ) return null ;
1749+ return template ( handle ) ;
1750+ }
1751+
1752+ /**
1753+ * Build socialLinks array from legacy column values
1754+ * Used for reverse dual-write when legacy fields are updated
1755+ */
1756+ function buildSocialLinksFromLegacyFields (
1757+ data : Partial < GQLUpdateUserInput > ,
1758+ existingUser : User ,
1759+ ) : UserSocialLink [ ] {
1760+ const legacyPlatforms = [
1761+ 'twitter' ,
1762+ 'github' ,
1763+ 'linkedin' ,
1764+ 'threads' ,
1765+ 'roadmap' ,
1766+ 'codepen' ,
1767+ 'reddit' ,
1768+ 'stackoverflow' ,
1769+ 'youtube' ,
1770+ 'bluesky' ,
1771+ 'mastodon' ,
1772+ 'hashnode' ,
1773+ 'portfolio' ,
1774+ ] as const ;
1775+
1776+ const socialLinks : UserSocialLink [ ] = [ ] ;
1777+
1778+ for ( const platform of legacyPlatforms ) {
1779+ // Use the new value from data if provided, otherwise use existing user value
1780+ const handle =
1781+ platform in data
1782+ ? ( data [ platform as keyof GQLUpdateUserInput ] as string | null )
1783+ : ( existingUser [ platform ] as string | null ) ;
1784+
1785+ if ( handle ) {
1786+ const url = buildUrlFromHandle ( handle , platform ) ;
1787+ if ( url ) {
1788+ socialLinks . push ( { platform, url } ) ;
1789+ }
1790+ }
1791+ }
1792+
1793+ return socialLinks ;
1794+ }
1795+
1796+ /**
1797+ * Check if any legacy social fields are being updated
1798+ */
1799+ function hasLegacySocialFieldsUpdate (
1800+ data : Partial < GQLUpdateUserInput > ,
1801+ ) : boolean {
1802+ const legacyPlatforms = [
1803+ 'twitter' ,
1804+ 'github' ,
1805+ 'linkedin' ,
1806+ 'threads' ,
1807+ 'roadmap' ,
1808+ 'codepen' ,
1809+ 'reddit' ,
1810+ 'stackoverflow' ,
1811+ 'youtube' ,
1812+ 'bluesky' ,
1813+ 'mastodon' ,
1814+ 'hashnode' ,
1815+ 'portfolio' ,
1816+ ] ;
1817+ return legacyPlatforms . some ( ( platform ) => platform in data ) ;
1818+ }
1819+
17191820/**
17201821 * Extract handle/value from URL for legacy column storage
17211822 */
@@ -1761,8 +1862,8 @@ function extractHandleFromUrl(url: string, platform: string): string | null {
17611862 // Full URL is stored for mastodon
17621863 return url ;
17631864 case 'hashnode' :
1764- // Full URL is stored for hashnode
1765- return url ;
1865+ // https:// hashnode.com/ @username
1866+ return pathname . replace ( / ^ \/ @ ? / , '' ) || null ;
17661867 case 'portfolio' :
17671868 // Full URL is stored for portfolio
17681869 return url ;
@@ -2583,13 +2684,21 @@ export const resolvers: IResolvers<unknown, BaseContext> = traceResolvers<
25832684 : data . image || user . image ;
25842685
25852686 try {
2586- // Process socialLinks for dual-write if provided
2687+ // Process socialLinks for dual-write
25872688 let socialLinksData : {
25882689 socialLinks ?: UserSocialLink [ ] ;
25892690 legacyColumns ?: Record < string , string | null > ;
25902691 } = { } ;
2692+
25912693 if ( data . socialLinks ) {
2694+ // New format: socialLinks array provided -> write to both socialLinks and legacy columns
25922695 socialLinksData = processSocialLinksForDualWrite ( data . socialLinks ) ;
2696+ } else if ( hasLegacySocialFieldsUpdate ( data ) ) {
2697+ // Legacy format: individual fields provided -> build socialLinks from merged values
2698+ socialLinksData . socialLinks = buildSocialLinksFromLegacyFields (
2699+ data ,
2700+ user ,
2701+ ) ;
25932702 }
25942703
25952704 const updatedUser = {
@@ -2733,13 +2842,21 @@ export const resolvers: IResolvers<unknown, BaseContext> = traceResolvers<
27332842 try {
27342843 delete data . externalLocationId ;
27352844
2736- // Process socialLinks for dual-write if provided
2845+ // Process socialLinks for dual-write
27372846 let socialLinksData : {
27382847 socialLinks ?: UserSocialLink [ ] ;
27392848 legacyColumns ?: Record < string , string | null > ;
27402849 } = { } ;
2850+
27412851 if ( data . socialLinks ) {
2852+ // New format: socialLinks array provided -> write to both socialLinks and legacy columns
27422853 socialLinksData = processSocialLinksForDualWrite ( data . socialLinks ) ;
2854+ } else if ( hasLegacySocialFieldsUpdate ( data ) ) {
2855+ // Legacy format: individual fields provided -> build socialLinks from merged values
2856+ socialLinksData . socialLinks = buildSocialLinksFromLegacyFields (
2857+ data ,
2858+ user ,
2859+ ) ;
27432860 }
27442861
27452862 const updatedUser = {
0 commit comments