From 8dc41a22b9dd985a846be965c3beae4fc7078f4b Mon Sep 17 00:00:00 2001 From: Michael Sukkarieh Date: Fri, 6 Jun 2025 10:50:13 -0700 Subject: [PATCH] Fix repo images in authed instance case and add manifest json (#332) * wip fix repo images * fix config imports * add manifest json * more logos for manifest * add properly padded icon * support old gitlab token case, simplify getImage action, feedback * add changelog entry * fix build error --- CHANGELOG.md | 1 + packages/backend/src/utils.ts | 44 +++------- packages/crypto/package.json | 2 + packages/crypto/src/index.ts | 4 +- packages/crypto/src/tokenUtils.ts | 33 ++++++++ packages/crypto/tsconfig.json | 11 +-- packages/web/public/logo_512.png | Bin 0 -> 18511 bytes packages/web/public/manifest.json | 14 ++++ packages/web/src/actions.ts | 75 +++++++++++++++++- .../[id]/components/repoListItem.tsx | 8 +- .../web/src/app/[domain]/repos/columns.tsx | 5 +- .../app/[domain]/repos/repositoryTable.tsx | 2 + .../[domain]/repos/[repoId]/image/route.ts | 27 +++++++ packages/web/src/app/layout.tsx | 1 + packages/web/src/lib/utils.ts | 31 +++++++- packages/web/src/middleware.ts | 2 +- yarn.lock | 6 +- 17 files changed, 216 insertions(+), 50 deletions(-) create mode 100644 packages/crypto/src/tokenUtils.ts create mode 100644 packages/web/public/logo_512.png create mode 100644 packages/web/public/manifest.json create mode 100644 packages/web/src/app/api/[domain]/repos/[repoId]/image/route.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 33499cb0..eac72664 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added seperate page for signup. [#311](https://github.com/sourcebot-dev/sourcebot/pull/331) +- Fix repo images in authed instance case and add manifest json. [#332](https://github.com/sourcebot-dev/sourcebot/pull/332) - Added encryption logic for license keys. [#335](https://github.com/sourcebot-dev/sourcebot/pull/335) ## [4.1.1] - 2025-06-03 diff --git a/packages/backend/src/utils.ts b/packages/backend/src/utils.ts index 1d95f0fb..5157c022 100644 --- a/packages/backend/src/utils.ts +++ b/packages/backend/src/utils.ts @@ -2,8 +2,7 @@ import { Logger } from "winston"; import { AppContext } from "./types.js"; import path from 'path'; import { PrismaClient, Repo } from "@sourcebot/db"; -import { decrypt } from "@sourcebot/crypto"; -import { Token } from "@sourcebot/schemas/v3/shared.type"; +import { getTokenFromConfig as getTokenFromConfigBase } from "@sourcebot/crypto"; import { BackendException, BackendError } from "@sourcebot/error"; import * as Sentry from "@sentry/node"; @@ -25,44 +24,21 @@ export const isRemotePath = (path: string) => { return path.startsWith('https://') || path.startsWith('http://'); } -export const getTokenFromConfig = async (token: Token, orgId: number, db: PrismaClient, logger?: Logger) => { - if ('secret' in token) { - const secretKey = token.secret; - const secret = await db.secret.findUnique({ - where: { - orgId_key: { - key: secretKey, - orgId - } - } - }); - - if (!secret) { +export const getTokenFromConfig = async (token: any, orgId: number, db: PrismaClient, logger?: Logger) => { + try { + return await getTokenFromConfigBase(token, orgId, db); + } catch (error: unknown) { + if (error instanceof Error) { const e = new BackendException(BackendError.CONNECTION_SYNC_SECRET_DNE, { - message: `Secret with key ${secretKey} not found for org ${orgId}`, + message: error.message, }); Sentry.captureException(e); - logger?.error(e.metadata.message); + logger?.error(error.message); throw e; } - - const decryptedToken = decrypt(secret.iv, secret.encryptedValue); - return decryptedToken; - } else { - const envToken = process.env[token.env]; - if (!envToken) { - const e = new BackendException(BackendError.CONNECTION_SYNC_SECRET_DNE, { - message: `Environment variable ${token.env} not found.`, - }); - Sentry.captureException(e); - logger?.error(e.metadata.message); - throw e; - } - - return envToken; + throw error; } -} - +}; export const resolvePathRelativeToConfig = (localPath: string, configPath: string) => { let absolutePath = localPath; diff --git a/packages/crypto/package.json b/packages/crypto/package.json index 2a6a185e..abccd406 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -8,6 +8,8 @@ "postinstall": "yarn build" }, "dependencies": { + "@sourcebot/db": "*", + "@sourcebot/schemas": "*", "dotenv": "^16.4.5" }, "devDependencies": { diff --git a/packages/crypto/src/index.ts b/packages/crypto/src/index.ts index 7e5f4196..8f6ca211 100644 --- a/packages/crypto/src/index.ts +++ b/packages/crypto/src/index.ts @@ -79,7 +79,7 @@ export function verifySignature(data: string, signature: string, publicKeyPath: publicKey = fs.readFileSync(publicKeyPath, 'utf8'); publicKeyCache.set(publicKeyPath, publicKey); } - + // Convert base64url signature to base64 if needed const base64Signature = signature.replace(/-/g, '+').replace(/_/g, '/'); const paddedSignature = base64Signature + '='.repeat((4 - base64Signature.length % 4) % 4); @@ -91,3 +91,5 @@ export function verifySignature(data: string, signature: string, publicKeyPath: return false; } } + +export { getTokenFromConfig } from './tokenUtils.js'; \ No newline at end of file diff --git a/packages/crypto/src/tokenUtils.ts b/packages/crypto/src/tokenUtils.ts new file mode 100644 index 00000000..be5a064d --- /dev/null +++ b/packages/crypto/src/tokenUtils.ts @@ -0,0 +1,33 @@ +import { PrismaClient } from "@sourcebot/db"; +import { Token } from "@sourcebot/schemas/v3/shared.type"; +import { decrypt } from "./index.js"; + +export const getTokenFromConfig = async (token: Token, orgId: number, db: PrismaClient) => { + if ('secret' in token) { + const secretKey = token.secret; + const secret = await db.secret.findUnique({ + where: { + orgId_key: { + key: secretKey, + orgId + } + } + }); + + if (!secret) { + throw new Error(`Secret with key ${secretKey} not found for org ${orgId}`); + } + + const decryptedToken = decrypt(secret.iv, secret.encryptedValue); + return decryptedToken; + } else if ('env' in token) { + const envToken = process.env[token.env]; + if (!envToken) { + throw new Error(`Environment variable ${token.env} not found.`); + } + + return envToken; + } else { + throw new Error('Invalid token configuration'); + } +}; \ No newline at end of file diff --git a/packages/crypto/tsconfig.json b/packages/crypto/tsconfig.json index 39b3533d..b364feca 100644 --- a/packages/crypto/tsconfig.json +++ b/packages/crypto/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { - "target": "ES6", - "module": "CommonJS", - "lib": ["ES6"], + "target": "ES2022", + "module": "Node16", + "lib": ["ES2023"], "outDir": "dist", "rootDir": "src", "declaration": true, @@ -11,11 +11,12 @@ "strict": true, "noImplicitAny": true, "strictNullChecks": true, - "moduleResolution": "node", + "moduleResolution": "Node16", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "skipLibCheck": true, - "isolatedModules": true + "isolatedModules": true, + "resolveJsonModule": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] diff --git a/packages/web/public/logo_512.png b/packages/web/public/logo_512.png new file mode 100644 index 0000000000000000000000000000000000000000..6028d76393abd359c2269b318fc0753ff3de1d60 GIT binary patch literal 18511 zcmd3OhdUf^)b{M^orn-!3PO}b3!)2&5`xvc=ru$yyLw2F1R=VJ-g{>eghcOMB6?YU zvFv{1_kGv2%@h4AKxPoshr!EHom2qU376bsG{O^UL zoQ@A`h9{&Y^JWwGqSLmLYsAWHlhv@}QTL(6X zzQ(;zq@%xSB8&e$%4tLW-Z3lxrr62pV-MRkxHK2qn3Ln4SYNv2lKA?`eD98PhuYep z_!1g@ zX`6w5Y3Pc$gdNC-gj=Y}pWB+3jo-dm($Iytu>%Q@9FJQ~u>_0Hd?Q3p4k6~k4-Z-O z?#I6%!{d`TxC12I27iBZ*|^wS{$7bqHp&_Sl>y1IdsBbCDihM z*W+6Y6;g_C(dv^P-5Dn=_)r#bOA{-TiCdILJhnO_;8(qOLyUCr4)COZ`@n2D@T|h_ z43bcO3w59#K{T%jxcxToa+Si`KxCiPyklQxh9d5QJ1EM&Cx#A!4P~&p1n&H-yNi9t zclV}B1fY4RwY9bGJ}R#D{jwTFR`@CchL@`Va6`)H;h)fdm@LKFfN8mBGL`J?A9$ch z?yB0$#Yo&Ks|NwVJ^nWwajt`>q+!fJx2WG9b0ovT2nVK9Y*5nYv&*eFauB4lpJw#o z&z^N>Dt1u;UC%_!Z)!rg?=Wqn8R33cK!sHa$HANdP}Uaryy1BHjGgrIUPg@(vM@Tj%2z4*bIRriPdkjhx{jJcuEZ!9Yc5 zOO?#8O8_bLzV#0xG~B|94oS(te3XAc1fYMjv?yC869ZlU60hJ7IWHlIU;OO&;^&rYfjMW zH8|tnhm~3qFY`?o2AQr5>E8en9-vsUT&R(UPIM4+UZ+xUh{I1VQ^9+pSLgaZ9H6wH zBZVS$$;rv5@ju%@fc^zzP68Jeh&fqk@5SR4HlTCGgZb{IjT-c!`3lx_bII!>#~2^@ zGx|l1lPb2`k}o6<&_r%uwR5WA0BT()dSe6!LN3%waR3U0P0VAlSXW|c9)OVt+yjuk z$-D!D-s1gR0Qe9lT|$6w!BVD*K-Mu8Fd~!$gV3-+%_n65McsdT1d2`2WfANo1{^5F zhP_(;n2c`NvpOhJP6j}5voYM90gx$h0Ac%zCo!d*IQkzoV(QF{9keQhuxo|-yR_%J zz{V9GKw9GiLwt=+{t5eUwQiWXn@9E^4{(fy07f*8JhI>a?{`-YcNn680uo>X08R^$ zbR3^Z06aWA+vr6ZV7YXlTFUY!4Y;Y%c<&AbsGv9xkba!__qiJMUxOKdq{tcf&UfV3O8pXbj$0R`fGQ3EBn#e{o12%(^B+Tys&2N# z0D^@&ybqDh3N=4u1I%rGy8v|yRzhM=0;J>RIthSCA6{C52g^U)Q2;*8PLKeC?tc$_ z3L-x$Na8QYED75@{u%=X%(=pS8Q5#+5Kf|!8UU~ZEx&tvRth4|1h_XcSK}Z+^VXAa z9lJcq=)C(r@q2lW)pDFgtr%>MWK(Ew?71tlabh=>b7Zfu}XRI5)B znuLHC4iET{;PW)-BX|r1$vdS90l*Euq}jvj(AQTofcf|EH!_G}81T(4gcdM=n>)7Y zOZs(~0Ab881~%tImS7n^u?axD{h9)h-3+UACb?emQwL;U3~2uhbGeZv_A;Ix0Pa3a zR0%H{Acql{yAjC(fRgbv&+lwf035+r0B}=oTH-6X_5o~8^nIsvwd<+03rUTb13Mr~ z<3oqIP5Mcb0O9j=ksSb{*^_>WJW%Wu#Yf(g1!P}^mB!z$;I3P zWnZlx`9gsCcxplUlFGPl%hbT3w!u~Rt-U>6W#!1Grl!j{CQ+w-wyUcvmHaWRvcHaw zPC-=_c;oxwoeB| z(~GarOG{K_x0plLpRogoQnCq_k?j{+uKXccAIzo^Hji)f8B{4!NgaROYue_QI-RyM zYzt__UGWi6?AulFbY%GN6n^Ymp`oYRWc2*Uyaau(M{zTgArycU83g$X^yBr` zISnog5+?q;wHGJl@Y3}Rf0yb^`Ure_I!ZkJt*x!gex$AR(QOvrf1ebFmLfQvuyQ*3 zxtzrEKtvp{%nd~j@;XhIzG*mLOFd~t1!@dEJ^DaMNO+mqy>ei}+9i<7*VW(fX5|=* zZa1Sh3#gRIL@MU;nBxXzg4|-hl)J$0+@2tPhepNva)`2OcQ)ti`IIX>rO~tY40lNR zT;yD$2~*IDS=mlVuaH-dcW52j7RjqrNrbslYWx<9B7`Dxhyh9d)PW0%=7kE~a??`W zSsZRSQO|T!V^+v$`&nf0RdWa(X-^R($<(hfR$_}A=ZI-*<+n&2TJ=17^rE(8lYGX+ zoM>iFqHZ_S_6UzLiMcp>I!}g4^+mdsuEXdK)&;ACyPv0hj!Hm@GtyROQ2J;}zH zAy6c7wPd09!VjxOYWWf+*nwe8aj9ld9KX7>f6Q9i`1go6R+lpU|HclLu6wdvO84^Z z_Rh{RSH~52v(RvxrRvN|$0g2FKmAbGhk&uifJlmTpkF}|1RrtdunTc{P9Tm-VN7Kx z*J;rb!PMEKs7@UQ?bTK8@kVrdB}b-ozWdKWCh0_tlgxq|+m3Su&S|gg&gx4Ai36}l znN#gJOgknsGMiBrV0b3yiDVKjcsX8_9rS@SZ9cC{nniELWdc9=@&6rY4=?S?o_?>P z>Ni;Jm*U+^EnE#F2}d8WBbM2HrUL#f-b;braJI0SYTKkh5d;{eRd)DtnKCCQq!mQ< zdT%TXklS_EG(G26=2NyH1r!HAk}`TbUOJ0ss=HKEQy*X0Uq*5fzdCIyHEtYTaygq@ z!q(wD-MR=P_}U)?tR5Pa_LmHg)uT7dykjS%=&1_w@2bNnbD~LQiD#Mu9nYLyu`98X zgG)(EZFLbJn0^iNmJl)r?05L>cMwHJko1>MDG?xPu%q(jQ{@Sqxtw-Ay;GYTL!xIg zq;o&UDzcyem%)mQ6=rgJt}M_?@a6EC5qH+Xl8BrL(reiNZxoks(4`Oje)z)o(1Fhg z7R#j9gJg;4A9Rle#;S2s#tkr z#(t7=Y8r-HPnH-2F;sl4tquK%u;}Z(`HO4(lk3>_uc?i4Tc0kd%mzylfFJ)V_>&?) z9NgKbV0PZGcicf0G>mI;3uXH7u$hBM0a~ZC|Ha7Vpo^XZXud~(6iG1oT5k*0{`pqs zCJZ$yUZloN3YGoJ>vSn|-luV4bWXT(C5-K+Q=6=!b~~N1rG*MUCX6IVKuwxum}IBM z7K~mbnkXpvBA;^r36-5axDYb_eT^M5M)-$VbBg*a%7^&#e%qx%#LG%CCW%M^0e zYUE-$SD>PMI#4rD|Bep%(=Fkm|gy*|H}UFT~=ki1Hzf%#|{1u48{1| zg>-?%Z|B4lXB(RQ?Z(*6ZE1jhHNnM}fOGd&%mQkSb2-`krINpY^R0*af;DfbD}Ei; z-A)uE|7cL=zc^9gWHUCh5T>^C5Gs57!P6r2t}7gq+D0UXzb-Hj$6Z~V55e1JvgMKd zl`05yke~00!@^0>mkY}&YM}qQ8!*tkc*5^IT@k$4-W8&r%-^yvUuxQ#at<&&Ixh)F zq#0%~4=S+!i}G7e%ap_$8tOQU15}_-Q)=VnDb0qWx;uwGe9ldUeH6yh4cz-ZaZbx}1Sv zc?NLf$!+3R*l-OO7Z=HA+Daiy3$`*CgA*{3_V4=HHxm(z=Mc!BN5AKC*W5A^Yz;z$ zUG>YukqJFBle$(o86W=@>Vv4{lMP2RObQNT#SPr;VItK}HOaAU7uB6SJzDIHB_3$( z;Y@!S$&V#$z8c7sF{66AFFSLLtv`KQdcY)P^8x;0FX4gqrLwkmJR>8cj)B4EkYj~u z6-?aE-@n#jg6F?SUdMK!$#ZLGXMS(#4-ck(&T4nIwy3c1f$zt@^}{BMAbC~Y8q!t1 zHP4l;zv>cyBb_BSYTn4d$<-j1Eon+ojrHss`0G(*sosb6R+F3Is@tj_`n<+qN5Zb1oiry++gfOmjKN zo)!(6tGDgFY||4;uD@_WR~IK(l7MUHo8hZy%>YImJr_2?z*rp+S-AV`bJccli^N@IEI9_~BSj1`^E@4%qKxZQhhe*WpP zZAgUAJ*MW;E7!Km!(SH{Q|)EjWzB=Q+3pC5 zxpY40eO&4q_^WXdu4!3Axcl>N+<5JjX>ngX7kzHW601L@9G&G%m9^gzW-(uxE%0si z`o?E*k2Sl4zugK)k}4g!q(~S4$*pm-_3?#CRBdy&f83npW66(tO#&+myoBR*WvX9S z4Mbe#m4~F}%Gz>89JWfPn$+5xJWLoh%iB-N`mIe(p61^c(SF+#Dy0JBA_ktx18zDD zc*qii>dQc!iNj^+Vy#HUwHLV(A_=MTAEr){w+Qt+8W3~*(;1Fk=O0KXE&_Bb!4NY- zr8^y<9?S69emm*;pwWDx$gA}fOML2;A0_t=E7q8_-DDKj(^F6&KgPiexCh~4Gx`LtJG zU9|b=_ZJHON6qX7O8VXSsSS_IC!jkG3tOHW&3T*0IOd!&wynwAOGEa9#|y&QmSU@x zSJ8=i=0Z=s^goKF9qykjKqEqdWx+YA!3M2P${vjOoOAsEqaLPBmE-=#!CW*RnWnCL z{rav7v0yvs)-L0m=KscaZY5rsY9+qdJ=k+df8%W$iFK+yWm_tVHXbE|>|qD}+;>%^ zq+pFsvFKZM?}6p4<<8cF9;Vf3^Dl5`=Tft_x*p*t7n9FA%Ir=n4o;=e^J7#H{-hpGrZgZk-bE2zq!wb?N#r^foXmy zB6O#-mcq&jEu+W(*W7pEZrszqMQ zYh91KGR=)`Yf%`Z6-v;aZIvuIVX^d;LL+$~b6_x%aAFur;iXp#}Ym((-B||IbnaB9D{X}oKA>#iJ58q>b=I%f_7`qiX7s_(t`Z*U+-+oLhElX% z1>@n$++wv!9-&)MCt}zQoP3*Am+res37*!t-u=XIv(I^=IWZW+<f*Ug(R3GABD~ZL{|Au^BFNBXDdgnN+|GZb|ZO_k;Yjzi@I@*KbWthGe%M zy4^9KQK3r6H@A1{`oS}iUSSL?O5%0L)AF`0M>ul;PkLNG;MTH8 zR=)z7uf^l#2hnuAVTU%T?1O>YXpL=H_-FTL66rJ*eBdFCbqdBNCh46&{I2TM_s0vh zVGg2Gcz*;pzl+}gH|RCi80;?IL!Aq{3{CHP$w*&($?WhER{x!Bt+@(|P|d{95{?-y!OLK3U(ETMob4zMn~KCx=|Sn-wr4Vl<_b zn`Yf7?Jtvf#(0_`?`%^0-(VGtJC4^M|De^_U_tprcHVo!I34aB(ZI&iuAa=doA%kM%*>xzumhJ?4g7a1Fz+=es~}wJDR8bzgF%P^Wa^CPr(bI#js7WB zJiBLbZOd;pY_@9V81SqPc6M$qtg^1I<%1I24`s-wAJ_ql=2E+Zr>rO6Rp)VN>>&@T z689;0=^yFdUC>zT>Lv@v&^t)lRFQVOkvk?q=d0M?zD7z!%M{yRyozP?W=ReBGhs#i z*)RMjUzK)wfkXbyxom`jgHyvrw?NPzxXV&belRx~<1Af$zVJtZKUmD-SV18} zj={PYF#{!-{l$u}F*NpMZv3Ml((l539STsoj=-*lb_t)00I#g4x-TXvtxX*?MKg5^ z^(hOBUpTZ|dEZU2(g&X*iLHX1OA_z0#Dkq4%a~RQ$h=z@MapCAXwU>7$q?hq;C<_a zR+{8!nBAUg3O*e*DPJ^8J;g4K)6T+RMi0!NfTLLz@fHM0Qbe6os$VRw;p^wLgP?&- zNt)kQp<$!@swQ<7QAh>Z;!0ra?b55j_>05zTuCVVm#H8B6nDM_GxIsuw+MzgIc4{hhIU-TYqQ4ecEqouXKmP?g;kYwX z$|zPk33oxdffj+D848+o<^E-@CO{1B$vBFjU^*Vl#Oe?+yCFbehswm~Pq^RNpUA5t z0#cm190U7Jr1j)cJtB3JZ8)85>%8E`;CwRkVKmwdy6>YwY39$&{~HX(*NS^pf^+812jbuHb?D#Q5;oKDF*o zAk5Kq8TO^b4rbpie1wolKjK0Ul~IXyZK3)$>Ig*qtOpoTF;ZCpb;76-<7Y}^v}13_ zVd*K@)2*s4?u2R`9=a5IagHay_&2t;90EZvy6-ZS~2vV)p`APQ&*1xr`A&Mwxe{cF_etxfdf^W5|DxG9 za(L(~6x_`0YD+J&Zi%A0Gb#jv@kTYSXQar$+t^62rwR2GVf$)ate{{3KBB&Eb$^V^ z+jV@*jHP_iGkxpC7SpRzzqsUh$?HxskR~W+E1i7%LKKA=0Yd^X7DSM6vd%;!Y8|I8 zmfp|4ZsaS*KlM|~lr{lF(i)l9Zs1MFn9*CZb{O3SS}YtVzQ2Vl#Vyrn@xmudD0tmx zlbuf%J?QT9o~LH5%anSBylgGku04SrV^tBrazO$j<9xt1yA1}TNR;oo?k7t-P zZ}NZT-NkU~{t>~>gxLtIkN0`kI;ts9#K5Dd!JvB*Fb{blZFVu5bezL>^%FPW7FkMl zJNdSd%~?F@?(ny}u}|b-Fqi_Ie@hOT3kX_gK9X4NukmZbwDkKu68O_nh35+=aWIRd z&zkx$Ri&p}yb{hhQTKbe31(0pnK{+;yQu@M-1asXE9;~^)P5&vlr_G0JKg)Vpo-_k zikUq4M23q4$;;nl_K|oeon!)vZQx8|6ah`uvEzCev}46~RE*a_8d{)Bm_`D;G2-|< zF+Q#n!zn=I(~1$jpQzPNIG(w^pP|y7C;5%no5pCJjY(4>;M=K>zZ#bbi~raU#ShY; zOYLL@LVEiBuytLg%F4Z;pOo#s8g}!T*6OIx?6j%G#iX8k{h)lNL}CiIAhWL zwf@4{_4h0i8wwK&lFpAmzznY&D+M+dxFH!n)YA}}3gs~OsVgD>z$Er^Jrmy6zG5k8 z%T5Y|KoO}oV?5Y`DGe+jfd z9ry4&N3ti~+*7g>8;fBQ+p7A-KX#pMkc7c8b4w9^25EYTS3>%G8ij`OjQMtd#CYEJ zmm0JmP{l6h9rQlWnc!CN5~@h;1jCV&t>W?vGa>$l71ANT3g85s=g(A0osIq$mo+(X z6HjMy5NC0hsK>49urTEq)Q;8QWKK3){Qbr+GZNU2cLRGV;oP@CG>23dqCev|Q3PX@ z^eTaOE!$LjkWvYP{y!Ico{5_=(>*i8otP=)>V*JGiVuDMO=v!nt4__c!)csum{56b zp9#chS8c_}YLv5e|6oQurY|9{VCu1mWXa3V@aw07$e|#Ty{2PX^5$K9qH1nnetaQA z)^+3I8t~x`+&WlYFa|?LzV+7%7XRFSCrw(t%{-U#seRSwK74;z0tqKXf+re+yg5kO zw(;%#+ip5N@&OWa_vNw&0W8jT%Mna2!)f9k{Xt5i5c9V`;!WTE{-G0}l68=1S}^a} znwP$qi}%u1>uQ=2H#EGTL=RfXH%371&&9I;USluYFG^7=%dRXFy)%gn-?gtzV5+~X zq*<3=K?5i$Kh({R@}QO2bQBlaGsrF4>d(#`*-zrqg&@VfXh}GUSV3q7`&dp=kSkg4 zH&3t$tuXe~aM5Qa27B1??ES6t7#R<|eO;|e9`!)wdN0W=i|QVkirRR=;1PKO_At^Z zax?D*&x`#?nG!Y5>X`_!Y2WZJ=z5w*>^0JLZk9AQ5AXmv_rB;A@nkRL$G)<5mY<1Y zVNAwWor>G#^$hjdk0!i{=2|3PcRF2>jsTdS0n^6A>?fzETOiIhoQXZ=!eN{^o<0t- z2qvNB+(5x`&Fzvo1f{PZKbQhL*f%`c`A9MfSxh`$Q1?w~CO}u@z+ocpeWAyoJBy#Z zN^_?EX5c^z0PYK(+NZjcsqei&TLHQqcjb4nnSX_%!S^!xGmkKGX?(_&{#MK2HyOyQ z>r<1L$bT%?lK46*95eDGa4h9?<8(u9k6>9$?)ZO}%Z{&$tFZLXu zc_Ve06QhDACr`KPz>wUHA(j1cGgf=f0Wyb&12Y_Sdr+~JXJz2&EWw=c+zeFD)Hinw zQ?CtiOKLUwyNF|YmoMCCS+Zt4oLg_AwF%F(La4@(0{yxb9We{foobQs8q$Tmte|&E zbCdt~@(0t44o8PpxY3;kw?CcP6e+|y(=?}EeSQ$WFS}D)$D|8K#WiJ;4-3~_wr6)P z2)yVZ>>(ldLwgttLMbJl*DrXbUh_&<+wIuml#~?FR7vF7%KpqpggUeZBw0Kceb*_% z0rSY2`^o5j+<8!KjlfhW{nW-61c@M>98?@3|(KmB}+5ZJ&$^?KYikNVK*Tq&9L+TjK*~ zbedNUvHX}0#GvzEW}{YJWxsk7-dopx zcP(T+`^O-KDHV%2CWcJkR)hX9nsw)M_x7&eYeq@`M=yAHRIA8wc!~H|XQ6Q*q_eRc zO66s9gFYVkHN8yJnOG^Y8|aq!vQ4z74U+RN@Y=ArGtAx1|4d!OgV#yN5L?A+IFpzQ z^Mx-p;Wt*&5Ryb)3jJoU(xRfGh&D47e8eHW#=pe=n33Zdn8eC0d88ef7e)hu5_MiV zm4|f%AGrJeTo4h+PTZ|2?%pqJq7v@ifF`{YLpwdQ6x!d|HO z?HWrMVgyu|+=OJktt+9{zl#Q4>lWCj(_;KOSQ(4v&3A_3?9-3->)WqN7cqrRRhR2=1MDpsLHr{a^jnmAzFvQDf0B6D1@o2P~PBU_FEk?!XB zw&f0;=4)_vp#kPKrz_}R^4C~L)HN-~O70CSfJ|YYEcIn$MU@JIAXvn4vY@Fc1rW6R z`TBBL&5|Ywzwv4dS&D7+SkV_s&la8B%5G&nkX5S#k%ionI>nI2b=cJwFmieX$kZ;^&%BRqzr zrpydp^{3WILfG@9)+aYhwzb?}xazg*J~Sv_MRQIZOmWt>>XZ%|$2GZc)5YB{dA*#b z|LmpJN_@?d-sI-~Q7;WNI_#PolJ)f|Cha&}{dFadU%wR8%<8iYY0qaS<{V4J!gr>(b_Nj@YYg|(h^6b zz7j@9eQKn>&T+mb zC%Cq$!Wf*8t^iu#NZwf9S6S4*=0)%e*!C`}c=XlMbHST}olnS#83@9=@I*vJ3@glo z;lr`JFYS~WGXqa&uzO*X$+t3Gmz%OU&cT$qWoF9FNRZsRUBG{60H>86+n8(Oubz^p z*rue?@Ci7aNgh3SslBW;bTmVKJLomK10a3F^JxiT!PlHJNL`k;Gi9^1O8TGfWM7?{ zNQH<|Cs;cdua=eZXSd)Jy`HHWBoT6ady02keNHA%DMs)2C+e(#@GtpE+flfw4S1iy z&1ps`(BJ>X0?C7S9rR=bUD)H^fO!BpOUJhRP;W+NI=%T3Q}TCMhSLk%3MyPDQ&BBu z#D2n`0~w`)(9==T`BA&oSYqnub~XGJBFh1uZSu?gnO5|o5ggtLo5W%TxxMr+DL8-2U!4 zoWEYFYA}U=3!MObXcuXS6PGz{W7^W&>MgrT|4To69ZPR4Yc($Ty*_>F48!XJR!b`w zMQ|6UyNM}fX^(ko1Niei@rqB5sT{-t)#(5NVt_c^{F&@95{ri8>fvFPfu>I)v&KVG zE7b`V&&i4atDgH4m))Ww-CTYv8o91+t;KTb8nD9r;>@h50cwESP>#A{5!V?`b`^iN zL%2K36MPMK+daPBZ&We1!ogMsJNZ#Eh2?^|FU}Q7rB7N-mEX{O2Ox&YUjD6K@!5SX zpCqbO$!@87Tg+)%7WMF6h($qtV6AHHLDT4xbt^EO?l{&jqdt3lUye4E76a#{( zorR%VJH|km^6<~LtDS?{(r-Oc7Kq>gLxH8a2kf!{cja3NK0SSdr#m5sCF#7wKpPoCMIsKlL=y&;NLPoIe?3J79@K+s;wQ4Es5*S@{y9|i5{THKK+4~MsN zzD(OdeWND;;m{|`Ek}c6LvXA|Bl472wgDf8=n=nNs&D0Z2-B)#cH338r+EFh=y^je z7oiFWhaQ%$16sqkkA(~^CD`kER*94n@D_ImAUeuO`jj#1eU zN$H-$qMdTxreZx2_4$hqY6LB(Mn;KNb(N2;ZA}l+?hZh&&%U|2d4R{zX*;_}o8V#b zz0aR=GeC{^OVx?6C^%`EU!EQCh%n*Is$NHZ`(f%`9FSn>mQ1r<-DUrGoD<~xt%B6& znTYkzXB{9Bz%l?vua(*FxC={4-mZOQCo_MQ9CrH|9?ZEYTCa~F(3HBbq?pbe#AA{r zJd2FoK$!}LEoYRr*0f&LAEF5%&NqIVqxd@pZ7_#qoTw2Y69hrd-^q^@a&1vvT){(kD7_UwBb>c`Uel>}B)onwT!Zg79X1NY!`RUG)}D%tpIIyx2$ zL6D4ay+JPI&;C>Xby{YENkppV&MKA;&znOe)+7d z0u3H=PUallc%$HG?=?*VD|mqJMY@RN7ur@A$4mHWb>_JR`)?S4sh|0-tTzaCvk)Nu zP_lec7~mH>TNPWFi@NTpXlcQcMHhnOI^TAUA`^oZ=hB3**3(3BThJ<*RB) zm4UL2AnFdrd%ArWq~AwW1zP`&mtd|==QN1pmQxJ;RuxNM)?Zkm_!&}@zweQh6JOst zeFH!6uA#m0-)8T)a3Di|9`v%6@9nv?XFo*$L74k=O=8>ErBDYl-W=2T!6+mi)y>YX zvab{6UPUWxwTmW`>5jG^ynkt^+tiX=TO*PY;`kBl{gXy^1}=^Ku~|@h5!wH5l@S`! zdb+#x7Hq4eNQuRe^Uic7h^X9%;eHrclaDrVhfZFB0doadz-MGky#tp-` zjn3b19R;0DF199$uc*yq@}>HnpRn6qmMVLJS>P7vqz@uRjAxj;q><{k_$L3AkO~V}IAmo&`rG5vLnY3(o+zFq3=NFrHZ0o7IrYj1MATz~mt% zB{lVGC8RW)CrQCEWNIYi2n^DsuRuQi6em@t1B4Lb{>5wD>NgYY{a#(G!7C$U%2Zw4 za&K3w;Qa&fN>!2C#Je{{cQ0mVTY1DjYUjaa@xJb8ioB`L^f=5FVjy>auAl%$@f+;d zDlYeMkuQ@u24H^NZuRGyc#=_Wo-xSnoG^8C6RL~TJ%xioN4mqMx*P4&-I}pFzZ}K$ z3Z;Rv$N>KG_Ow^UQ;&ZozPsPb_Vae5{>%7$uG->|Kf?a{ciVTqQf%q#6t~MXsuWJ8 zh;SO^zU|@px_I|~ZLF`H|L?EgFYX%FqDGnv?ML&*h(pmbD9k8$-|jRJOJ8kjY=b$U zo5Nk!?E@lO2pCIh1|G&|j$0?0tl>)bZI(lp&!>+95Bs=0J4v_-a&o%Bhf6=yf>vhz z_t!4ibD?{mkGw(HaILkKk%uwyiC-i7S+$pvppy-1?zC3Q?>OJS zwAs1jaci^vIZ|T6#(PY*M4`AwuF`x_;UP-oG4083)hmY3KHke& zVR{aqE5Qo}sM~J?+w@f`yoMfrEi%9>+w@I09kz&IPK~Po+5!`ItHFoE1;=m-Y!@Hd zbVDr2=+QzIOk0Kd8H^Vu^j-ZywsI@AE(ZB7Pnrw`%0YMyBxyf;J{(@%wK2>^z^nDP(k(a^a_6{T)QGD{HB^0S2d_x zHuF+1GvmCdiLqr_0gH=^WdcD5@3ETQAj>EDwjuUCEq!QIhVH>59iU(NPtlF$TYEU) z)ICYhk}sa6`$3fktHzF(QjI<@{e^o(eB&-yE>Izki)FcABK@)Ew}>G7na zju<~ivly)|U(FZ?$?i6KU9EIao$UMfY<(ej)BXU08Wfn)07W4z@pRR&hK!h$WsLfGA&g0eCRRLjxj`aY=T@UeS3 z>Bm=g%CjRrdsKUVlfU#ia?<&>=HlsWTvX3(PP~Kr1aelK2DZa)!tn!$WosU*@- z>>j3huN{;>Y-+#{HIAzvVV8ZUyG{s2d}8TMef|tjxvBp7{=0K%Zpw4}0Jl(3=4j)v zaA9vKvfor>nHat973gj<;^SKWrmr%k!7Ux#9cE$jK0F3zCa^a$I#*oFQ9p~xR5^=5 zzpIERsU|tmdApTsEw$y_tC)_hm!5d3CMaN@5LUF8{4!rb=-n~sQ0=gn=L$kIzvPC6 zc$^TWZ>nW0zzD()cZHsU5HV+Mj^#W4{SgHcdag&h=k%d4bN_JR_V=1Ckn>D#4i^@MA3=J02_Ql8Ren}O1?nW;uG zRQIBW$*z>)l~%o}!FW!ae}3Unt%@hbagk8-B8G+XOFIjEZq*Vm#Y9FO71UsM@ME8I zXI8{+Y84k)tEXC4e#nRuG5srG8Ol7wyFx+1EN;_8+1#vJtDZPm_TfdYR<^xZA@JlU zV|`AnM1Lj=cHU#C(AY^F%N7=#tD1BXCy8qDsFrb!F}GM@BbG8Bm{b1-)0}u#%_eWz^xam&dYjgo!D8Lgw2~<^ z)BJvG?Sw6-@t!t`lXbuGRQSsA^KFvmnja`WU)*HMwEle!v#mrN7uELFEn%zx_Kj>g z)-_SI=x0p(LBvh1gYT?+hS?LSv~Oss&ldJO+*#1&itA7#lG**a0Sr>%EO)--hXc8W z`^_DVT99|XGXkl<9NZnEm(G$7p=J9)^yxD}p0;9snfVEcBJy4f-GmCE{c%&(8ENw- zTnkOFX!E9A(BQS3O3OVr%f?b_I+=3fR+Y*#8H#@vgJ^e}_lJ=_J=hcRo3aGJGh5dH z-lVxb1W44H{jzjon!APH2!^6Z*gTC79xf^5HbQ;Q55B58ZO(FrqA%Qc4SmX8#~@NE zPpp1Z!cti=^yLQ{0_aZUhA53_lso%KIbEFaR(E@?J&KmG0RJXUbsRIDQ;XHFnPhTW z%2P~D1a4GSfF;G8;HK*QwmRxdN=az@h**gXn{Z8>alP917s`S=Gy>}Tx8FIM)WP1j zk8kJ|F>vy-LJ?g%F=l#t`PYIsg~+3bE(kc(FTT8y-3 zP_2Cfs&NYE5j{y+^2#OaEo!7eOmuwY>zKk+iDjZ1%|XA!!zb0)P`1mgSa7Dy=JS1J z8X`sFWSPHFGWuE_|D#cLTKpFRTj54{a(RJGYFjsl1pk5?ft6j?3&Mq(ywq2G| zx#?I0Jd*?5j0JC+Z!eTGQt!w{98#C)7L=46ue;;V(cyJl((-Db`-!C0>8|8W>;-@*`$_gxVM(_;FjU%$J7ZQLG7=r ziSKHng1<+Xvlle`s;mths4Zt#|GD8{9@!C}T-;`qy~J@eCG1{=jPXWhZe^P?jA=XF zkeT%;r66g~l|DMO`PEmD*kt?&rd(#3nECa~q0->s)E%@n7xk=SqxN)gs$7`uFSB{E53DN)@)j8U1{k86}2`Y z1Fy^4v3}_Rhkao{FMbT}_gA5lX{$JjbhV!hoY6guxHMtM-+K}laEzB3+z2vt%u(~=5>N``555OFOGBC4;?HTQyaIkB@sNoN@LpqQ~ZV#?b}FfMJ=kT0!^GM9g-JWnR{sKNyq z;WyvQ{2Ahn8Y=l(*5%?lH63_(_c6WQ;qigW^xR~2y$h_x)Umhq_leFud;~S|moyfy zX$$P5YA`)x11q^+mlw9KO-@gZk8ebAWekVYn{MDJllLPnqhYUGP2Z^atIe38+Uix+ zg&dS#zo>v~PJ}U9^AO$qC3%b;uoXG_`AGf^H6VXy`fXlQ)2!=9r=#1LS$?M?$Xzam zvHH|KFezv^Sgu-v#K=T?xS=7jdGA_+4?=q9Hmw3GScW%2 zjWQ@yh*+ylQ|y_cq3zPwspQJS?9cGZMen{XQuQE|1gytFbzb71aCOrw%%uk%ksTck zpX@t=*0HRP3lP8Ci8C;B@4JuP4C#pAc*qo>FkDT_h|P(Uo;90-|Kh8+0|SGdoyfnp z9nMx#Qszw}(-*&cu|fZ-m?6)e0VRaZx;8%FWt{OGWPtHT9i)r@Taz%H_o#d83TiCsrKec zG7jzUKe(v~d~iREMaxNqv?aDe`XpP6IU=}p%AF+AZSsB!;?o6ZJ@Q`;COQWFzAVUd z%%zG}k{ta@?c91-DNdVtF`j>185gKVK|!IgM72WkbMn)YpiK6CUc>MkQFCDnB@QbT z4h76P2V7-Sf6SiS?fjb+Gs($$@T6r+fbG{-qQAc|HA>G#_!gm0l7X)MZfZ(4?cVNa z@|(n$UvS!mI(9C^_z2>;Lr$ysxt)*MV1zqrrowRSr>+l`(_AAL78Tt(9@CftYc}jz z+<$Ul(Vg@AM8v@T<@R(ZCAeClPYfe(Pk}u;xhJDq*A=5#>fj*e+|6|Cr+SVhi)eC@ zyq|4rYJVa<ymvGuBP}}zK9*}AsVm_h&G{b?oeq}V*joIDw}{n_uLngTQLLaR6_ zT@W&5xbVH{hcN;RT|ZCf;cS~XMeg^WPrVFO*TNfEND0)c&l`xdAKg*v{_ARNROZ6M z@>Gn8?sB{HyQyE9_SM$@;#0aH4lBiJRw7eti@2h`6pYz!ileZwRigbK4HYX2S@x1gXt@S?oMY!z3( zdg}=pUjG6;y6_}cqV~H{!UzAAqvpplf-bl3B*y*$7BGS_3P;9;0@O^ zElx|*e6hXOTAt5Zo-pHLo+Mt8MpfFpxRL2XSpQ4sS4WG@j`d*0jgE`7Rr~x>Xh+Z; zFTOUcPm*cF_ZI@?8#>-)3uI5}ByZspuFL6g=WfDQCfrW__|Deyw26OZABg?OF!hHL z5HwGRE}VoYyTy~R$^oEkz|DnR_^`{ieBv{`F82ySWfpUc*u8j?Zr;CG>{$?TX_=0- zXMgiK7f4y{C+$8{x4^qP6JaqtEGD#`yjLSSmKPkLWPqaaFXrsV)Oo{rKb1^;@G1S= z>$$;Vmc+=V3(BpkME<*aXAEJ`zkNeXTPJaYg9$F9lS_)xB8(Q}ob)THR#z9D5aYM&E03T_grXIim zzO-V8yqG<*@jxt_66nDY1Zjg?5EXk^J??P@z{ZVXS)>x={LFpio6j|}xpjcq7HYSz z0Kky(EE+Ma2dGJ;XKV-h$b)s(%F|pp0(motpy`8NN_Ty*nRU*uZZg7YqX6?{FuT3559jn0+zyHyyxEdPnl0T=P3GKyXKr z1Rwa;!+SMEPYm?LlAZR;k%QICKWAE{w{mfS0zkk(3LrFdj?ECHvcXQG{$J49>Hwm` zKIgby94ZUZ1$Tb3>MpK=GcqSO=WWSGWcsl2-vHRj`Hxg^nY5w`z;rbDMhwu}c1nOX zJg}hI@%H?F&OTJF1}MgED>2(4>N*AxFPYMB?K_8 zOm+_PW~M`=Bmd9b92p+;99{GEzGVnQS<~K&Yp$d`_GMz2(sH-?ZJf{J{l&l^XP=xutc_C}b2Ve#>IJ5B!W?z3c2R+;N}d)H-oZiWDpZHMJ&|LNfY zZV5e=V)S`W%CGzPmY)S`USeiFzx8dcJ0D|03vfA*8F23623KPmBZFJh*)R6p&9!b~ z#SCsU<|<{R`*)vgWH1oYla6_IU8zSyi^0{zLjS?*dy4~s+x9O$>-}E5!XLP6r>jSi zk>N(-lt1o_&J3<;&P>4UsOHVenDF~eoaK(4{@no~Aq;MUSAi+myX@F0_dUnvZw_W0m*IqCGC?6Zb;B{mbq4=M zAFSJf6ZTCE4FVBQY}YM%ST4+Xpb&Jp0r0q#?mhm%69!H(C;v$Wc6HUb!gyuWS1~c% zxjgOsbN$cn0?jwgS#JaENKfG9uMoc_d9Sr>8L;&FbFWTmV#J%;l={g=P7E5!&DPxK zKHF{G%kqqq!R@$Ge#WQsQ>RYdzwn}dJWIob4_ZvUH*?D7eYcpL$i$$0M(U^Z-s86S zcKqu#`Ss8Nc%(}HjD&UH6W=`uXso@b11u=Sa$h~QfBE%Oe#-KF1)vjJLOFnYtDjYg zf3bOgx_J-xuA9Ft!WkB=b8YV3YuA3OJAQo{`jNW`i1d6=jDK9|DXi-|MMSj5H}L~ Rot*@7pQo#z%Q~loCIF>KKAQjl literal 0 HcmV?d00001 diff --git a/packages/web/public/manifest.json b/packages/web/public/manifest.json new file mode 100644 index 00000000..acfd2f05 --- /dev/null +++ b/packages/web/public/manifest.json @@ -0,0 +1,14 @@ +{ + "name": "Sourcebot", + "short_name": "Sourcebot", + "display": "standalone", + "start_url": "/", + "icons": [ + { + "src": "/logo_512.png", + "sizes": "512x512", + "type": "image/png" + } + ] + } + \ No newline at end of file diff --git a/packages/web/src/actions.ts b/packages/web/src/actions.ts index 9cd3ff55..9f60373a 100644 --- a/packages/web/src/actions.ts +++ b/packages/web/src/actions.ts @@ -7,13 +7,16 @@ import { CodeHostType, isServiceError } from "@/lib/utils"; import { prisma } from "@/prisma"; import { render } from "@react-email/components"; import * as Sentry from '@sentry/nextjs'; -import { decrypt, encrypt, generateApiKey, hashSecret } from "@sourcebot/crypto"; +import { decrypt, encrypt, generateApiKey, hashSecret, getTokenFromConfig } from "@sourcebot/crypto"; import { ConnectionSyncStatus, OrgRole, Prisma, RepoIndexingStatus, StripeSubscriptionStatus, Org, ApiKey } from "@sourcebot/db"; import { ConnectionConfig } from "@sourcebot/schemas/v3/connection.type"; import { gerritSchema } from "@sourcebot/schemas/v3/gerrit.schema"; import { giteaSchema } from "@sourcebot/schemas/v3/gitea.schema"; import { githubSchema } from "@sourcebot/schemas/v3/github.schema"; import { gitlabSchema } from "@sourcebot/schemas/v3/gitlab.schema"; +import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type"; +import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type"; +import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/gitea.type"; import Ajv from "ajv"; import { StatusCodes } from "http-status-codes"; import { cookies, headers } from "next/headers"; @@ -1712,6 +1715,76 @@ export const getSearchContexts = async (domain: string) => sew(() => }, /* minRequiredRole = */ OrgRole.GUEST), /* allowSingleTenantUnauthedAccess = */ true )); +export const getRepoImage = async (repoId: number, domain: string): Promise => sew(async () => { + return await withAuth(async (userId) => { + return await withOrgMembership(userId, domain, async ({ org }) => { + const repo = await prisma.repo.findUnique({ + where: { + id: repoId, + orgId: org.id, + }, + include: { + connections: { + include: { + connection: true, + } + } + } + }); + + if (!repo || !repo.imageUrl) { + return notFound(); + } + + const authHeaders: Record = {}; + for (const { connection } of repo.connections) { + try { + if (connection.connectionType === 'github') { + const config = connection.config as unknown as GithubConnectionConfig; + if (config.token) { + const token = await getTokenFromConfig(config.token, connection.orgId, prisma); + authHeaders['Authorization'] = `token ${token}`; + break; + } + } else if (connection.connectionType === 'gitlab') { + const config = connection.config as unknown as GitlabConnectionConfig; + if (config.token) { + const token = await getTokenFromConfig(config.token, connection.orgId, prisma); + authHeaders['PRIVATE-TOKEN'] = token; + break; + } + } else if (connection.connectionType === 'gitea') { + const config = connection.config as unknown as GiteaConnectionConfig; + if (config.token) { + const token = await getTokenFromConfig(config.token, connection.orgId, prisma); + authHeaders['Authorization'] = `token ${token}`; + break; + } + } + } catch (error) { + logger.warn(`Failed to get token for connection ${connection.id}:`, error); + } + } + + try { + const response = await fetch(repo.imageUrl, { + headers: authHeaders, + }); + + if (!response.ok) { + logger.warn(`Failed to fetch image from ${repo.imageUrl}: ${response.status}`); + return notFound(); + } + + const imageBuffer = await response.arrayBuffer(); + return imageBuffer; + } catch (error) { + logger.error(`Error proxying image for repo ${repoId}:`, error); + return notFound(); + } + }, /* minRequiredRole = */ OrgRole.GUEST); + }, /* allowSingleTenantUnauthedAccess = */ true); +}); ////// Helpers /////// diff --git a/packages/web/src/app/[domain]/connections/[id]/components/repoListItem.tsx b/packages/web/src/app/[domain]/connections/[id]/components/repoListItem.tsx index 6fdc1c51..e2f8c55c 100644 --- a/packages/web/src/app/[domain]/connections/[id]/components/repoListItem.tsx +++ b/packages/web/src/app/[domain]/connections/[id]/components/repoListItem.tsx @@ -1,6 +1,6 @@ 'use client'; -import { getDisplayTime } from "@/lib/utils"; +import { getDisplayTime, getRepoImageSrc } from "@/lib/utils"; import Image from "next/image"; import { StatusIcon } from "../../components/statusIcon"; import { RepoIndexingStatus } from "@sourcebot/db"; @@ -46,14 +46,16 @@ export const RepoListItem = ({ } }, [status]); + const imageSrc = getRepoImageSrc(imageUrl, repoId, domain); + return (
- {imageUrl ? ( + {imageSrc ? ( {name}[] => [
{repo.imageUrl ? ( {`${repo.name} { const tableRepos = useMemo(() => { if (reposLoading) return Array(4).fill(null).map(() => ({ + repoId: 0, name: "", connections: [], repoIndexingStatus: RepoIndexingStatus.NEW, @@ -35,6 +36,7 @@ export const RepositoryTable = () => { if (!repos) return []; return repos.map((repo): RepositoryColumnInfo => ({ + repoId: repo.repoId, name: repo.repoDisplayName ?? repo.repoName, imageUrl: repo.imageUrl, connections: repo.linkedConnections, diff --git a/packages/web/src/app/api/[domain]/repos/[repoId]/image/route.ts b/packages/web/src/app/api/[domain]/repos/[repoId]/image/route.ts new file mode 100644 index 00000000..6d967adf --- /dev/null +++ b/packages/web/src/app/api/[domain]/repos/[repoId]/image/route.ts @@ -0,0 +1,27 @@ +import { getRepoImage } from "@/actions"; +import { isServiceError } from "@/lib/utils"; +import { NextRequest } from "next/server"; + +export async function GET( + request: NextRequest, + { params }: { params: { domain: string; repoId: string } } +) { + const { domain, repoId } = params; + const repoIdNum = parseInt(repoId); + + if (isNaN(repoIdNum)) { + return new Response("Invalid repo ID", { status: 400 }); + } + + const result = await getRepoImage(repoIdNum, domain); + if (isServiceError(result)) { + return new Response(result.message, { status: result.statusCode }); + } + + return new Response(result, { + headers: { + 'Content-Type': 'image/png', + 'Cache-Control': 'public, max-age=3600', + }, + }); +} \ No newline at end of file diff --git a/packages/web/src/app/layout.tsx b/packages/web/src/app/layout.tsx index 55925c89..9b213695 100644 --- a/packages/web/src/app/layout.tsx +++ b/packages/web/src/app/layout.tsx @@ -13,6 +13,7 @@ import { getEntitlements } from "@/features/entitlements/server"; export const metadata: Metadata = { title: "Sourcebot", description: "Sourcebot", + manifest: "/manifest.json", }; export default function RootLayout({ diff --git a/packages/web/src/lib/utils.ts b/packages/web/src/lib/utils.ts index 4e416ac7..6d3913ef 100644 --- a/packages/web/src/lib/utils.ts +++ b/packages/web/src/lib/utils.ts @@ -408,4 +408,33 @@ export const requiredQueryParamGuard = (request: NextRequest, param: string): Se }; } return value; -} \ No newline at end of file +} + +export const getRepoImageSrc = (imageUrl: string | undefined, repoId: number, domain: string): string | undefined => { + if (!imageUrl) return undefined; + + try { + const url = new URL(imageUrl); + + // List of known public instances that don't require authentication + const publicHostnames = [ + 'github.com', + 'gitlab.com', + 'avatars.githubusercontent.com', + 'gitea.com', + 'bitbucket.org', + ]; + + const isPublicInstance = publicHostnames.includes(url.hostname); + + if (isPublicInstance) { + return imageUrl; + } else { + // Use the proxied route for self-hosted instances + return `/api/${domain}/repos/${repoId}/image`; + } + } catch { + // If URL parsing fails, use the original URL + return imageUrl; + } +}; \ No newline at end of file diff --git a/packages/web/src/middleware.ts b/packages/web/src/middleware.ts index d20c6c5c..b373ff2f 100644 --- a/packages/web/src/middleware.ts +++ b/packages/web/src/middleware.ts @@ -33,6 +33,6 @@ export async function middleware(request: NextRequest) { export const config = { // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher matcher: [ - '/((?!api|_next/static|ingest|_next/image|favicon.ico|sitemap.xml|robots.txt|sb_logo_light_large.png|arrow.png|placeholder_avatar.png).*)', + '/((?!api|_next/static|ingest|_next/image|favicon.ico|sitemap.xml|robots.txt|manifest.json|logo_192.png|logo_512.png|sb_logo_light_large.png|arrow.png|placeholder_avatar.png).*)', ], } diff --git a/yarn.lock b/yarn.lock index b379f320..76773d74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5803,13 +5803,15 @@ __metadata: version: 0.0.0-use.local resolution: "@sourcebot/crypto@workspace:packages/crypto" dependencies: + "@sourcebot/db": "npm:*" + "@sourcebot/schemas": "npm:*" "@types/node": "npm:^22.7.5" dotenv: "npm:^16.4.5" typescript: "npm:^5.7.3" languageName: unknown linkType: soft -"@sourcebot/db@workspace:*, @sourcebot/db@workspace:packages/db": +"@sourcebot/db@npm:*, @sourcebot/db@workspace:*, @sourcebot/db@workspace:packages/db": version: 0.0.0-use.local resolution: "@sourcebot/db@workspace:packages/db" dependencies: @@ -5869,7 +5871,7 @@ __metadata: languageName: unknown linkType: soft -"@sourcebot/schemas@workspace:*, @sourcebot/schemas@workspace:packages/schemas": +"@sourcebot/schemas@npm:*, @sourcebot/schemas@workspace:*, @sourcebot/schemas@workspace:packages/schemas": version: 0.0.0-use.local resolution: "@sourcebot/schemas@workspace:packages/schemas" dependencies: