From 2a111eec41b4a9290dbbcde1da80e0ba38566d62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Kurzyniec?= <5943484+lkurzyniec@users.noreply.github.com> Date: Tue, 11 Jun 2024 14:00:13 +0200 Subject: [PATCH] Version endpoint (#330) --- .assets/api.png | Bin 22341 -> 24788 bytes .assets/core.png | Bin 16592 -> 17049 bytes .github/workflows/docker-push.yml | 3 + .vscode/launch.json | 53 +++++++++--------- .vscode/tasks.json | 4 +- Directory.Build.props | 2 +- README.md | 19 ++++++- dockerfile | 6 ++ .../Configurations/SerilogConfigurator.cs | 2 + .../Infrastructure/Logging/VersionEnricher.cs | 23 ++++++++ .../Properties/launchSettings.json | 6 +- .../Startup.cs | 5 ++ .../Providers/VersionProvider.cs | 24 ++++++++ .../Registrations/CoreRegistrations.cs | 2 + ...de.NetCoreBoilerplate.Api.UnitTests.csproj | 4 ++ ...tCoreBoilerplate.Api.UnitTests.runsettings | 10 ++++ .../Logging/VersionEnricherTests.cs | 38 +++++++++++++ ...e.NetCoreBoilerplate.Core.UnitTests.csproj | 4 ++ ...CoreBoilerplate.Core.UnitTests.runsettings | 10 ++++ .../Providers/VersionProviderTests.cs | 27 +++++++++ 20 files changed, 209 insertions(+), 33 deletions(-) create mode 100644 src/HappyCode.NetCoreBoilerplate.Api/Infrastructure/Logging/VersionEnricher.cs create mode 100644 src/HappyCode.NetCoreBoilerplate.Core/Providers/VersionProvider.cs create mode 100644 test/HappyCode.NetCoreBoilerplate.Api.UnitTests/HappyCode.NetCoreBoilerplate.Api.UnitTests.runsettings create mode 100644 test/HappyCode.NetCoreBoilerplate.Api.UnitTests/Infrastructure/Logging/VersionEnricherTests.cs create mode 100644 test/HappyCode.NetCoreBoilerplate.Core.UnitTests/HappyCode.NetCoreBoilerplate.Core.UnitTests.runsettings create mode 100644 test/HappyCode.NetCoreBoilerplate.Core.UnitTests/Providers/VersionProviderTests.cs diff --git a/.assets/api.png b/.assets/api.png index bedabcbc20b6fbdf6a21b3602c75ee064e8b234d..729ebfd4627b323b726cfc12307f41469a1a443e 100644 GIT binary patch literal 24788 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 ZTMy-_zL4Dm{)qr&sCWHpnU3?r{{xik&LjW; literal 22341 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 diff --git a/.assets/core.png b/.assets/core.png index f4932e1cddab99c8ed66012a097be35cbe899e9a..c1d2e5008ec10e2556dfed91905db5e14ad526c8 100644 GIT binary patch literal 17049 zcmbWecQ{;syEVRviC&_n=)DusYetC{O!Q8)AX*5b3^IryQAck>h~9?iEkbmncM`pe zHjMgCo^#%Fp7Z>k?|XgE?~l36n0?Kj+57WZ>t5?#cQ`~-nV8@{0RRBRsw#@w0D#4b zd932y#{B2WtLGk=KUf~x%JM+jAnhjR1sq2` z6jJzVZI8$*Y*d4toPyQw7E6l>``&GS?O?+)O<^tHM|35a8;1YZj{ZDv{W;^?$M@ec z#Uy1^1!go)%xtIo?Uj1C>^7Y6ogw^&uABFbvS!v32W4ANwKC5QFh4^L2Zjw$65@aW zPHH>?0K!W~2_Os^3z&Q^PrqNH!ZU9R1)xlHh9(;WucCwd=OHJs=TjWud9l{But4F~ zGhO;XtLur&X7oKN(ZD&?rvU+{lc(eU6e(axi?gkZ?9~_ayxiqXlX|p>Z4HD@S=nNlaJ{FJ3#2F6ZuI$s_1R6-%ad zgrln#2dDDyfleA7U64Pg;t@Md`f?=#UFqysb=h z`$>vdXv&Sw)zg5KMC#3jFVZoYeW75k%DBji?cBRQDN z9*)^2Pzc<9mJ~m^S{S%;7lt>dwZTvt{ZU!&x${+xfRW`3SdMdNK*-O6K>r73tl;&C z(}5bD@tetjGHV$p`i{BL8l(hO7T19K#k4v9$l~E}n({cA z!4n*#2|h8)xpFrBGwbqwBLSmL&K~*s;Ri8CO1lbep9?)0MpDmXqIM5xQ{Br*Z~0xs z&8hX8?On|^O&Vz?ekRop*g(l#;X9-vRDhQp&%F*qk6*iQaA?Wzdzh7bEyY-uX-#XX zLw`Z(sa^)+l? z#|FGaUq(B~BN7J6LR3IaS%Zz1+X6_4rR%@1n;Z0X{v*5;`TwfJbuog+huOiH5{v*xP18NA^^9*^K^_kRAMQBlbV0 z-8X~1IqK*zAO0DH`vSDY$p}6hq5-F^-bqJ?6)2f;!NIb<&)f< zrskl~7PUy+dW09)S%pq9HFBG&dDA*C{o^vEwtH9lPtzSX(;UD1R+c(ARyaywrORYDcE+a1jJUY*3Ot+_CYyp+?2^)tA(0)wD0EFfYZ*U( zdy4?)iwf;iFcv@p`UlPh>}Q!@F#LhRmD4C%2}irtFO%jv(YJud438l|kkwlR0CZww zj*kHS7fdET2YvCJy}TT%9ayDMyAJ?QEq_vtb(=0+f0y010_96l*#QT4d52BeuStoZrO(wOKvN-$!t( zLN8Y-<3@07E8~z|s{}76O-X$Ms*hwO__xk_ru@s{SOC0-wv|;Y&R0hxUl>24JQYbb zcF|}mt*LSVg`23{0w{+rK_~WpS}-IJs~IFfFAu7<8ojv*1i7bdvn|Az%PlUm(NjgS z7c>!38DGihgA_rJ(f#Y{hlMLP4rG*#HfdfUrLU{>0PfqjAd$#)-UviH&77{NDdd3g z@bf-`v!NdVY#){&%rCnA(e}m?Moo`LESB$IZ|UFY@#h`1E)5H99uCTP-Uc}1B?44Q z0ALjBe_qCaW*bi6aphlLZ)&_EmOb_riw^_QaABZLkdm9zRxDW6-G0s(1h_%&DMC0j zvxGR{%i*ySp1!%X(VQ1x4Qv>;<*&PsAxZtHPaJD~(E?&Bjw@LSr{l_NA|mr6tema+ zI?coB@!ARdahKi5ZWu2M3QiDR@7~qTQa)m?OT1d#M>y}0wj`Vk{dYGw+)jIuXV51BZb>r~=H z(vO!S3i#6wRlv4Mb~S_F_2SH4g*P}|kx>DqL_8Sy8WWiJ)|@|Occp>H_uQ%^AI4!b zkcd@p$+pwwR=5ehz7Q68ATZ|C5t$Cey z3b=Z4AJ+&rFowB*01$-PSfE?~`xVhAi|5$M+xn)@$^Wpl{w(yxQGBIQ;4dz={p+~J zW54&YVve^2ao6|@tdqX4`IAxD9tFR&TkY=V{rF_9+di%YK2LIcs>bF9N95ZXobEm# zzR_<0=_*hCn8BF0TWRFy^^G@cPUxkp#iOohz0LKagmEc@f%A`2cDV`Vl~~f3u18l? zWANo*L>sn{0hq7(l|V@{ecB6iqhFgG<;jo47*Yut=EMY?_fyvw=9V@;tN|$Pd-+s& z({ubL;T?$2evj`kserb#U_MJx`W!8eE!lyuv0NFa>>aw-v*4 zbkdJ@+_d(v>nqDK9~u#-z|q51q@)P6z*Um|W!n~ZeAk|T08~Zv<7^=!2o<)2;95xt zSFhd`Yk5h}@G^3O57fRzvFSdy2X)elVvG5h}elxaeW>Io&OnPIq=VDHw^v4tnFTiGCnCvRmSNaNMc-O{D z17)3hNj_5X*-QrIqcd$fuLPCezDkIXgi{7nps34?7PO@LL%NQ#3_n{&P11?F{CNtU z!8wRJ6>Yv`c0X<4vw9N?O@GR%OUf7Ty_6G= zTmmUK_x-Tq#d3LMbs~zq#hjf_f_j!AFJMx@cJ66KuItG|?ROugat2MF)}G{{OdCoq zsHle0^1}n(hT64Q#4$koq>oalbUO#3N5^Z4MzR-)My;<}$tT_!8Ot~%vR{LSgb;|a zgdiFi5dr;}Xmn}hZNoJC8SKhZ)6nf@7ro4tVH~CJ1Ie5P?{}^374W!=O)_ee%cAv4 z!)kh>bQ)H$BHR2ydX<-3W+$!pOy}xf>hD?{!i6ND1QBJ5JKHM@GYqoSPhNnh145RopC;GBvsQ{1ssbdOEZ+E$@Q8?s zH8rKt2)($L;?&k$!Hw*j1FhbCS`#^%+wEGDKNG-RcU^o~{6bl6f&CKyzJQ6eEMVhS z-9ic$tLc)LNinrC8a99GQFrcMSr=5?xB50?#A^EU0eZPG`V#7tuSv>WxcwfiWlNy~ zJG6?lePfpUp&+eSe((VmTsAC#wwgrrMOoRqKNph$M~_Smv4?MB@y!t*5R@U)jfSZe zll_BTp?JLkN!k_>oETP05uf`*uf+eVLjH;u`YK>E4bB1A>$7Wz_-xJ=4t0&@Bd5*2 zKv3b`Hy{zAq}tYpTJ#z`g>GVj%O7j62T{}e_F)J>9PeKlc)ydHHnz?d_g~2O5O#mx zzER${eo7`i8Ik@p^^`E@f^fG*0JLggBb%|a&Wh8C5^DeW6(Ab?FDAwTU$I`NjV#&O zqZV121d^*~4wK#=KzS*)et ziPJ7RlGg?LgVFw~vI*(Dx{zm&Cljn6l_6I0(s&;@7>i5`)y>_qbh}MG#_NuB1N%JF z<)L8))i@GtYAk~5_tW2poOwg^EvtF6RUsN(SU|JTKaqL}f_o)kaoW}lZWu(^MaYvE z-Y5I4hMI}$P9r5Hj}Xwt*ngnz4gkp{|0Es%>ZbjFH0GZJXZe9F9%#yZUg-Z z@}N#{1~=i|X<z zeNQG%6HSw0hpU6MB_Bo!z|J~Zy@?`eVBO+n+@X8w-xQ*Q$s-z#89an-94==vDc)x93J^t2JCcH-a{D^`xs zmKr??%RvzXKf?czw+_MA@FxL79R_ADBFC1sux=P$Z!5d=J4tIPO=05VY*FwpchaC~ zBO_LCUZdRyWLf2KD_B7bS}HfZAFuayyk=mzzwS>+?vX( zlwjykoWA^-Y9t{fr2Z*ZLdY>mZyCi7a&d0=7M;qUCuZbydz?~^h_VoQAO%*NwXQ<# zyfc!mx1CsMv5R6T9vRZ(DfTW)J$c~WufoINKo(17;809oOPVkjVY@&cifF^KErS{R zE}I)Zlo1_jLbi`;nLm8tA}dTITjy#%vmi3mT;Dl*ZwU*Ry0VsHzL5)`89rNxT_d|E zut81&ckbqU=5en zh!|8jTD47GB^%XdR0r3OKf&AJtt&iZwn;DHmkgM}EhRz1dPA+cltZRjA80FknbzPw z*6Kec-yfNB5;a?*tG66%^6o2X_82P3`qY)i$ZUy3Vyz2^-Q}-KwOKx5ja30Z!}2My z->Oma&CqZ50%x5B=MEzb*dEVtEay7gz~Verz{A(#mxi-AwxjN``N9UIiXh*=L&TS$ zX!2VTxY7J@I6H{S!8p{xc19mkk1)QKMgTVXD2V><)p<7$p-5MqQFuQ3kZ**I6c`n> z0PFF#`Wjrh-lp;1OVztblFoa|!Y^1HL_unZNP3no3P!ujzVVSYU?BG8ek%)FopC-V z;ZAUXN@^5*GB*o7^^|%(SS`_guCVP{jxHJqz|~v-Vh;mZlJ=WHuH_uf9}Ue1IPr(v zxVv4W=&F3Za#u1G>!kZ&%w{#i>dB@MW(&G>A&VH@e&tzfcNL!(6mT{?wwQ#3oPy|B z*%0ZLs4emO4v(o*PhTaKmsGk}vWZ7W%e`Y=XVO500SXKOfFH#FzwwqIgSTcUQs}Vu z`KOUmmsrW+@*p;EmV#u1m_Dhqd_|XNY#=!UjN`d<%;h03k$E>oCx5oaApAj9p0x*| zKORAl)xXFp8-CY7>rDTXSRW0I+7+0)-`&{eh$&e`D=oGL@6o*rufRM;TBfWLl1i;O z8jYz3MF*;a2g#- zA4Z>~0!IHV7jvMH5*yKrq=Y3UFmpar)~JCISRu$udPg$YHcyS+L7nHzI4WDz&ghoI z27jZ60RRd9Hz;m`YEj&1mxWhMyi(Z{V;kGbxBJ)xuelhK`tok&U2^>c-p5bwbE5 zn?wAVgkBpA7w0CB?Q1n6KAVCcf|ZVa0Yk-iG-FRW$(hMtfwMqmUAKLVokLIKamKkH za&aCktnM=x{7#+Vo7rOLWMcpbd%xb%HI(SEJY7=#O+_r@5}MPU-_lg|yT)qX9J{yZ zL*i9u*>oP%(|JmGk%S7~sm=}0Ll)UnQ!-GLk_IV#hJa60zH#FK5LrTg&ep@WRWblK zK9mC9bo}kl)1=PUj#~h&A>-d=bj;f1u|s#7*e%Dq4s;4gr)7N@I49KN@}arwty9^= zcwi` zNGeQ!;!-Fn+tB#N^tTC*`;|K;0Gw6BmpR8x=r7$X7y z9fZFlUYO|PWuR3RvJ&~V(l+Yz`~1+{R2w2(Dt{bW!xH+_HV?1JL3)~i`~BP7UV1fq zk$qX`z28m0{>%EMlDJ9O*x3%xdH|$b;e2UAXZu`_hOMDZNC*1F_LIL5XAV|yND^y@ zQ{#yO%MV3a2%+Vv?gKJiRPigxcqcn0+jzKQ$N}z=eNCmy-@t0CB>z;hP(x35>oQ(gLKC>3 zyq~n9yeKr}Z-)^)9*fe=^|pyaEw&S`bLAmlo+s*SrRHe{ejUY5K;VD4oqYdH1B)d% zrKS|uH&fv8?7%r00%~xfx_z9Z!o#$X3$*%jpqG!GP zx29v`ys`Bu_uQGTootbw5w)y z^tc6ZtGkO*KUCA6jczGsJ<;*Vt4ik;{asIqsd;=r?Gb8(JDfy3A-o%5Is3x|A1VdK1 z$`CO7@ddN5Lx%zHuW~yQW>WPSIIqDZ!|0YC6^$;p0PEepaFj8~wsNVoPruG9?Hs%9 zR}KU`K%)Nu;3fHw*4-bp=1s>G>yfwqZlV7FaMhMvuw{?Mwr0}uD(4BO82$pfG0s}Y z{B`2HT?176HNKff(17a}ntAvfpm47=9bvfYkhf|G{18?v_2bLB47s8dbnMa%ODP zxs}`g>@dD^eAA1hy|SqUv-0NGLdR8Sj#kmM;@xkM%g7tc?38`Bg}lmXr-lwG_pj+b z`-w(=>zPJ!%F!={)2FEXs44Lo`3stwS>apFF(IT7hBnuznT`|cQulp zJ!4nTZY`o%2Ej#A;}2CKyGL=SVescjwF)1-k&QQBq%B|l4Jrr^{93i~_PMI|=5KQf zO8h1GB(BS>+Sm0;&z_*ki=Ix#*wq5onXPn29A{9DT6|(T8ta@%;h8!W~uYF zY&BnzM2u(mQ1jAy(7Q{(tfwrPqS%?*PFFHWh*dy za?pa~(i$GbWej87U993tD)u(l*tt?^x86$R_U#KY%l9)e(quS~2(3B%^QWuI) zk!3$L)U3`Yo=cB-Afr-u8Eb$x5UsJeXst}@xW~=Sl^~Eg>c~0WlAy!A=HDdui zNKgI;9Uhp9(cMij2yspZzmn}R_xhop2+eT&4D~)GPi^Xzs)=sT`Mr}=$4 zS}Vy=KQ4jY2m9Wmrowbd75=aCde;iO_+A2xmrbIvCaKffYEVrnH;(Yp#i*54){s!NC)_ZiRNZ-|tb(OMNu>1myQ9dCQ*CktM;zJmR@;A{Up`k9HsQErwI$GeK=LAT0bmoL6U>5@NiL}2N&}_5)|pX z;i61WjlpdGK^JX1J|Nq|nd+`O!Gu@i(D+?WAL{^)?03gRvZ5LMK0xwEG(nM7MSjEB z*Q7vK`AYfjmZNZ=`PCSrV^hII)X_L8=a0jS>1YfOMnYA(a6K_M1eR-4-*BC*UtT0EF(qAS zE>LKgSo@C^Svse9L7nOXqKlTQ50xEWmN!19U2T7kHcj*D-%Oqe1kNoPaK5w(<7W#y z$yIV=h0);^DeiEW{353c`i6s}M|mKmiN3}QQs~a6pag#Y1paP6$lV?^UbBn^Ac%UY z;8S`*i_@fkIG4GAB3~OFPHlO;WAkxzd;NP!Brt>h`L5gW5tpN)- ze^K|JIf}DB5_dIBUBE-bL@W@=$i!Xz*!1DJt%8{@gjee$Gn2M#Pekr5z}I$4pod4* zSq^i$F|C0Fu2scXIT70K#TktJFp6;I0#+(!+8r^~OxIBTXu1+y@F^%jq5ENwizX%9*ksvR{U;Z+{|JI65f z`^F0Ve9QX=?tH7_?q4+N-Vu*Dcva@2Q+O5WYtI=SQdr-=&V_JqQN=@DXNSGcoi-Zd zS*2XIhb}g21oRa{oWs5{3t^y6-MVV}b-4EmOyX#PuL?CNQxyE!ip(sCNL(X!hm+quKHiW}LKV9#Cjg$PH97JC> z1-x-M(WMg`;7oMK_;q(SUWWy=(2&>AWBvRo^d6y^E^DjF*+s$PvGuwS#jUC7&lA5b zWu`BKkpz()0Ib2LhC$|`MSy=jSewPe=iOD&1|{+3~{ z0cH+TBQ}R6OSCT~oJ?!f)@<|@(6(^b98v}8d8_s=P;8}@&}vgehJgEJSRigE#O5|I zC1r~-V%W{vl_WsvhANT?!3iK4{q+;(Eq+d8Q8-7jm;tE@EO@dJIC8wP=!ba&lRWu1dCEUE)PIB9e>ee2z@57PkuOe0MHa3Y*SUhaT%=7~c-Zt;D$IRoWtx5_Oe*wL|fu^QzYJ$C&WQi{Z!tY|xY2?9ad&Fj? z&sqXiX24;DgvU{K{qL{PqY+e@&)xuEX4#$77ZEpc!nMevsCxCBiU`%WE^Kfwb`{u2 z=uZQO?93@mb_%3K<_JTCK7*8gujAraa9rh~p z{gM2-a5!DibxJWpzij6=_~XFVGd`%(pwqP8*tSvXJ7P)(VN>zMyT89UO?h#pBK#3A zus9UqI83wE`__nyHvQn*$`)x23ig40rP%YI3zJrqSJp|T$o=~PM6a*EkG~Wv=Q{); zv?A_|9j;+qH7$(La+MJlSZuPM5PjqOjpf z5C$EV9$;3S1lAnFh`x@`)2Gf!@}l!)OHNRY#braQ^N@32YPjgNsiZ$Yv$2nAk~FXr z`lpaYk2h`wKB}wpm@JuTUE#77y3hZ}zO8Oj__xzN6kRMk=f#3LQ;YXWbqS@@`ChOY z^_kqv^ePr6dRtnq3`VP43I&{EbeltP4a4E2v%&tZH>AYr+dA;r;x8d}eAob+ij^}1 zk6ssSYoO@jsbqlf42#%wV6&Ya)psD_!ZvPsS6-I7eT1~^N}nsT2IFLaY{P98(8mVS z3#|<#Tb3 z7>yU6e#OixHBm!lmKrIpW&YjMOkeH5&OR|ZLjN8Gh1h;pAg2=V?40-QZ3W6aQ3K0o zn2mStSJVfG6=~X0W7-nAnT*B8YENY=H5R-a`*3~uZr7aSft7MsSbv@K^p3x8scc!< z-%N&DMxycS4kqP)I#)^ZVk8+#HL9nzyo>34T`mlwAEor|Bh#0wPZ?WD%FCv2mGbU_ zHBNPD`0?-2rXV(4?a5yo8H*>v;5fN_G_VNuLaq&a1_w)A6*-}Y;)?h5TgeMVWgg_4 z+OAw7mNU#DP5iO~+N?IRNM8>pv()#;_1KZU7%$t`uPBlD{;3$qSe7&5oIOoFRIL4we(md0<2Go~21l$93vCqu( zX^Yp@DH8NEKcfTshDdnnk1(zK$@~W4jJ8qyhepo)S;!`}xlv zWuN`j$xFm9^0~a{xi~WQ#s7*RVlXLl@#}Z0;a+u2{0BD!v)gi#@*AGw{V0PSVySH5 z7k`viIa)lfA-*t4S1;zW^f%rLBjumaWIQp$sm8Mr<9F_~*$!xnZftp1v-uJ~L#(SG z)_bwO@Dj%C`~wBO9;>+_iAch7Xd+I}N@Z$SjS24FdLi#vEZOm4+oC2!i`m_$d9y3ng0 zY`ZYCOrx>=KENKA=dbARpArK9ZC+xe+OMn`Y(Sj$n7<$(bB4T9EIDqL?OmN#*(+RF z^UwW;#chqQ!#zxY@4*#8QEh&WZU~=*VGKt9?>@A=y0TijZY-K_-nYZ($sf4Q1Vyd+ zH7kC^s%HZQHnyw!ij8)(BN?ny+*~WfXk!B#D^IplOu~^i+;_Dox}}$ zYd*j8{TErjWcjRa%J&EIOn$~VAl)-mcvU&rRLB3S&+{mo!W9P1dhxNv?cn(k7p z9i@1Eu)p4l?Kd$K^^;W|&INi6j`C1p=ZtfL-lS}6+Sy5pb>g7l&N&>BaUByUgvQZq z*4mK%lX_lW{ky)u^N&kNb2FaOKP5p9n8uYrXV;5%>Wbn~RnH(yf$TX>I{>ZId2`41 zb|IgcKDMY<6FfWVkz>r~zB0}rRUS?~OGTm->11JHaBRyHl?#7RVpJh4M}A_&6cYj! zKRd=GNL{s@hXwi^bKC6OYRZ^Znkn=j=#(Sa35t~NmhF^HQ}HzGgFWw@U{>K@3EcfpUsDHbH{^dYh08E#j-%wyu z1OO4)-r$oXT^gVDK+rrFfS?%YI#<^6}rA~47 z#rJo8-GuZRiW<8dFoUd*zq1oI^q8eoQyCnH~!8NL* zv^h|Na*aA!|k1f1<*L=<1WL81mrHm z2X5K5UyzYm`|*y&_b4S&?RE->6${YhQ#>={C;bVh?wv^pk=CC~iq25&I00*6A{;8U zwp-FU`~t>{0Y%D9(NGQ_9R`@?t-UwfhFJD505FvznXYSVrBd6iKf)pvyAk_&@jajo z@fc$EK8b6^oeU6^j>H(YZEtV?U3KxFYo18M>xdt)T{Fb?Kq4mu|s)sp7aXxJ}>qPF`Tf3=tT~0 zSqb*uunC2WiDwYUMds?gBL0!2H7_pz&U>>q7bd@+yl?|H^&lR8Me1};{;8$jQs;L5j~ILFB(~2Q|O+yfSBrAR;~sQeBufYgsCvI5gHIbD2B8RA2gYo>a#mh z=_z!^;-0Ti>B22ltiQT!M_wU#Xtj1oO~4OT7KLMDT7)O>R3OI`$0qe30Xr;L=1aua zx=E(Dqm0>wnw7zozA`P+R7_!gdD*VH^Y^`YdyJ|6@c3{IAa034LiA59Plv>}TAys~ z7wB=uS>I~H95>TSO-h20C_40AS~c_sjKRtogIP)sk9EwPeiS2Pl7vM$O>jEdEJI_r z20*_?A4!ZkP~eQuh#86P6DpMtt>^QINNa$237QEpq|>kgi$JO-sc~gds7vo9Qd&~)39(r zsE7n3<2zVxZ{8x*zLiE*EMc`_gLkDa4@+7Ub&jyz>i)N`!Y;?kZCk;64e_DA3f`nd zdi&464{Uyzy~6M=)lpL5>+hHIfu@gIufH**HU?JMWDXua&2bP5Udy<(hCiEvuv&ZI z_ks-WYLfE6kJO?oK4=fg7(M!m_*LJ>^hP7CxopLedh?6vfhliw*YzK;M?r|hfuZZ) zaP&ZHVDDuH)i{HUZ2;7v^AguK`p`~|N_Xm7F+F*bq5mJ#eNYiGBexw@$ghCM6G@IG zq;{lj?4~!Wvl__sj;~a@Lw`Q;J>q&Lt29|EKe2y;G{7>zYZFd0o`6Zj?eP{y!K^Mg zV;9d}wX*t=cfNKX>5PvlySs(#4W|h%==3O=AZdOmejGi~_5D*;MNPnL5^u#L z@NkWloV?q6(FPURAk}`FbAC}^*N|sP*g$Zy-;BO!{?em79|yVFzPxL+|1rKycI$rp zrOkp(v(p??Rv$&Q-tQxRde`eq!&a&{SI4xeE@f=I7PM6pFT6{9=gH)*J?>r{sh5TY zx|n^R>u^hYlNfk7UEz*FG2}p6hLT;%ZyycRE1Xy*V z+Ev>$2~5h%Kb;`MP+!UA>`%=TYfnaGBfO}d89Oari7N)o>StI~cb+qavUa5{uSKFl zPc)(wKCfG;I`<7tPbRFrkN#wY87y0u+ioJBZZ$N}cfFc9&D--sp@mPs=9{cYAT0Yt zG8MA->DE)3<-TnvSO&0O(7tjW@j9ED_aByh7a#(4@z>0^juGft6!+}EytK#^ZuO~+ z8*2=-Y&j3M^*ANfG8mDxyQ6ml)}6n6 zH~#SX#0H0jS&~=#gy%~r^D@(vzhevpZ0mf`aujdq%8{MXQ7xNN)*npEaV6c8JWi1U z&%65UGrOj#MX}7Z9yt>hq@lAen=5j{L^r!EA-rnJkvcOzn?@;jdFhb+2%RFh0;p{_&+Wnp$`q659cB~Q& z!~d8e6!uRR@y*)y{X(G}@ZO0xG4Z_p;@l<{*1M&K8DC5$uOQ<4!H%mjA)Dei6F52f zS(xx1&2DX4If;kzkx%u(=ZC$Q$i0TZ+(l#A|2ssOvGqgl8+@&A{3?|U^EI=tMvwX= zGTvd~oZLFHD4$Vpa{Tgos{kEufDz58lQ{I&;#6M?W=;3>svMI>XjNZUsHBR1)xK@j zuH9zjbWtOHV>!!VEQj9m?ZkJ^J}G2C%4Z7n2VZHjO=x{DV$u>sXR@qM%gZsOEF4e! z9A95Z=&vVz2t`$G&#iiLFFW9|8zp}EgjeK4Lg5q0NimqswJwoz*>awq`la=o|JJ$D5@x!&o9GMpIP_!lDp=w1`gMIitC2Ay6205_`&onJ5_7V!oGb#8`Jdx|ojS z(`3(eYT+G!L3SPS;^ z)fh5w1dGGH8N_|G)y^PYtQndGSrunVbde+a%lPXYtuD39P{Eu($yEj4qWpFW7Y)EUpxGsQh9>HhAr5sR&WM+v z6AXrU3ZA^hhLAM>k64Jhl>5K2BL{!NB7{^fraOcynHjN^Pg_(?WI`A@v^wp}(yHmU zvi*)!zz)09*8XYu{5qMMUGiNV=3>$xSt;Ot4BI*#d3X^~U_PC1>lslT^vlb)Ek%9* zEB~QK{}3~6^?#kn`^tAH@o`RDNXq;{ovYwc1?%v782~mtg z96o%Lizq^=LaXJPT#U=)P58pC{pWmq+fP63y&T`G^PLyR1$N%n<2(8sVFKIvnBl5g z`qU)Xg@-R6NN0S)i^#m>y=&_h`Lm>AwHfcYdaVH@YOi3fU-mG0wksB=(}Kf3@TjG# zC2n#$+0^JE`^1Ypmtk-HsBw*`Vh^Bg=h!dY+rddRjsGCZRPe<@BPJ_f|6*cjc=rsp-)Uub zJJF+{Bi(gf@HX%+(ZMTHWfn<6D9%Sf_%xP3zwWv9mDNB}bRv2?8*aR@csl+jSNJt}c)7Y2B|>LVAeP7hJ1qC87gUs+ zpR#Xtki?{A*pJMbYZt-eC%u=LYrpW9b5fu0=!O#Gc{%(F7He%v(S#uBFYa<{K=wT6 zsW%PU^4Hr{n8~k7FIYf&%`R{5<6|P-gW-u{p9#aoRXZ<*HP(pZ4t=0E>;qUp6(*Ab t00@S^Ox5;f{^=Dd{OJ)8tQMeef_^>hEuAv%!AxHOs!E!QW%8D={{yQWNYelS literal 16592 zcmbWfby!>Pwk;f_r4$HIiWCSg#kENA;>8_`I}|GtqE=7wwq`13;0>vF} ze*1ji-beO1XW#or9+LOTde>sDDPxW~Wr#tSbE;P%%cjkNALUDW)I>08~d}+#5ebd`AB)qvH$!ymI;Lg|uQp?hXKm zn#)Rvsk<8-rmu$S&&KcG89&C!C?-;AN!I99o9aAAAHG~-p!|w&&dC*#CpeI7&rfr> z#=$8&=os~esqb4%6K6aQhVA6u%Q(fC#K|GF1sCjQKjphoMZVcv$#bz!U@H(=7zo~7 z8Q2^`#)KM=nju$h7t3DkSHEko7DUDjR%^$rKc$QCdtLu&_r5~{h#F0=E4AN=Al_|E zuNw+p9s>Y=$PfTvPZR_IfR)hze!wXtKo-hBmZ0+|2TH;Z=dqb#@@v(xXZ@?KroK=2 z$085s$H&#Ru}){aPxt#G4@VZ!*hJ3Rz$@X$^Iws^ES!hWSUKIWf0)TG$SU<|q6uJs zh#yqn*b`EmAFsHo6qz5~Ud;x-u7FKO zsE)kJn;Dt7y$~1B>KeqF8GpLDV*7;*N=gR*R(35EDgDt~QPe$r^>|0_bGJKAcoRLY z_w>+D?t5JsdM~2^&vLbp{w)I%eoUXHoAC#gbWa^;5>x<>KHK`HGC}6zb(UFN#X-Vn z$G$hIzPIO3_W`Upnaz(Pk0(z)w~np(X8+_Q~??%$?j)kmV@yagfX_rrjpeK zSdQ6qKdFy=>gbnVc#_?0?K+)Ahkn5`CewosbJUnnN($uZ$Lg&EpmC&+OrK)pHd{y=3Mt>myd6ZncrO$( zr32gHM>&McM zy8j;a`utRm8my!D1#&I#ef=HMEOl5r^qr93mD$OM*aFfxx7-*bK_Prb?tAB!p4TPX zN<|HtR&-Hx{CM+hhkx7U7ikTSLcB`p5GBGYA+@+pjF+Pon8{m1cxP*U2PJNG;-TMS zT*@(qR$!BkYDdZi>~cT9*$z5s;(ftuE>%P}8!Ej19->%7>H(T>%{Oqw(63OrD@&<$ zI>AAYg4i8!zN>62uKEEtVn;sd2p9D-$y+~uJPQZHjEIVX4Sqf<{W?@s^!jpV)b%T0 zZmbij3iwy6&jT{3m~y+n5uO>P)~4@UL0@ylU2#aw>w>}dADBp^73D0Nobf!yLfB+o z%YY3g?FF_Uo4&Em7I|ypV;sUKY(KfpK_Kiz{?~@S_f4hTL{h~8+Gyv>X(qFN%(#)- z#yb-RWWQS2S&xa(J^ZZ|RXA4frEfoFC80IeEI;+`8A;23t7Ni#M^*5C>umw?jzq>Q z`}tyY&Qkb?&v7#n^dzipNgS>NEG{il4J6^UA3tXCJDtQ)FF-7^fu>v^Ky!i%KW}mt zFqj+g2jl7~u;+WZ_e2iN$GZx%qrxI9DY1LX9j3E2 zfnQw_Nwm;ZEY-RAknKa0(e`z4ro3Y#Lm_L&E?#&I`u+kcgv6j1Q=~oIo=AT<9~-66 z)Umd|61RGPETTm!J3B}R62=ui`9_{ly+nMt?1%s(8#pg3zwuN!@#IQ4LT}Q~7VUXS zd@WHC*q^*@Gq#ziqPSI4t+wIRa&Q=H9d`Mvld(Wbx=6f^9~RNuoFH4LW#6`wm(Uo( z;x_4w@rIaXiBlzlRpCWrwnkIo3Ddh<7<8wm%lGkNd~h@^Ep(a8JKPB+ z2mpYJPynK6e=QZugA%k7B!K`UDfN8dc$@24x94%kW!VTCV8n0e5;)#^x&pArvybL1@&U! zb9$Ub-9+#UiELodneLS}-4v4FNwEe9b6js>!KbS2GrV7yGq3A1F@t1 zL2i`!tJ0%5+Puhy_JmWUIn${M;8jCA%NcD8*vszB=hCj(?Q~5%?Tp^pn!5Om!`v4v+v|{otDTynD_Q?W_#f?IkYL z%WP5iSJ`aukmh2i3W1xl0b*KcbV!>;0o3jBeo}ry^5$^%25us52o4)@i-?YLATHpN zNTaxWv+^N7I*7?;n5qL*3_P0j>kx=?z_Z0a(d5+T>v(57=Lo2pEwVkX^4c13hZ%jY zP>j9g!&#v-b11A2{>+yjj1B`_a%;55qXN_c|0!|)lU(}0FWq)ag^Bfitm0$#1G|Ji z){uyTI!&r6;L1|zb{0#gfZr;N%1}!K&*O(u%T$y;mdw?ri?9TTPwzp1q?ff?&~19d z`@zcqPjP*Ugz*l@?n=O6IL2^qaq!V0Ym^xf(;8c3;WlOYemCw@Z&55=v@{AiBtar2 zuRMy8!%(WY9uyWL>^S%N`ighl9D|#b2g_C`Xz;laVe#gmi3VXXB9vJzV4%|1btJD2 zozBSpDt&o-)F~(HqCzq)OpKceQ<>`G=YPtQ`o!(UOii}%*b`IdaIHw5#4+9Krrl0~ z`ct;MO<)$0BSal0dCbSLj~KW7$UpKH@Oou4oM&V*af->}K;B|Rkro2CY-flU-?Yx6 z)+_(gm8HTNwya!%(_lSczyDt4X&Esz{NqQ9%tAzf7UrGr(O`4yh3`DAGmkBAFo1tg zQR09dP@7E$VP`g+yN<^V#{SN|blar`mI=sK04;5HzJBgFU1QKjDp?U>kp!-~D``(NX#8YKJ#7!s)d8UGv@m&o) za~C>uqSu_oaAYgLl%G09`I@J=+RL4H9k2dZ0T4?Akc5PR@NK_hz&~Kv|86?}9KTJM zcEi>t-0Xoo#~F{s+f&%S4Yy0NHJAXSms90Hro@OgJQx4ZyL(2G32FGA{9j{10#sIP zobO7zjV?xuUZlDywtjh-yZTMHvDN!)=Qr#diFAj{&g5~2kiFnthIsRsD5yX%=SUPp zkRCd1{MnmDru*~7oPU(ZAKa_W&g|jt{s_Y_@C6b%;f`q1BRj?s8BpJw$v8^@GwVf3 zH;gyPXS3?^boQpWE4R`ib76_>e($K5+yUF5V~G(*84VE$C=uTqDqXMM=xN^Em}>V% zT=GTVX>%$)8!%MuE8Xg!Y{OZ#RvoQAg3AzfbTJcsBb&@%QW?kq!N;%4X9m%XG~5mT zbuxFkkps_ujtW&n`%(@K6pIN$Qsl_6?zLsMbQfM?)sdVEgfHU+WwJ~d!$g00UE}Hs zRbOGgLHJ1?Q5(y6cX*;qphL_fcPhb!&8< zrCHtA3@V+jrQywCd91DFlFS=eIsVn$1oS>`{>$er@~ZZV(W z>_9mC)dw5ZVviSV45Ul7$oSF_4(HsR-%o2?#3 zANd&DU)rtCz)?c57Z$qup`t?MaBBU0=g)~HIYx57fxEl;6D)C6*y(;GIBD;!=<~=|v>u9=IH?RIoR*s#ilJ-Yp}ltcGIVZ3%c15}@T~BGSD2jA+uL?fbH+YZ zbY>_9EbS67#aVbjbdNv^q(I>%JB9S!{7 z3sR)a&e|9b)SD}a7>+ya-X_FlB#~linZ%iF`O*q@g2O>VJIb3uRZ$>45;EN@nYi75N#4QC4Yy?`C!F2_05#~Z-=OYFcm2p z8d)JYFUU1|k0>ORc{W#s$!OG|L}rt83~wJPd-E3$R6$mJ-D%@fU78`*mNBTJLQ^Hc zAWK#|JCV5+cd;G0R&bLRCM+2$Olsq#enm_b7VK;xG*|3v9F0{I6?NoKn>GaRMDETX zA4Fnr{5yUT1LcqgdTo}DSr>zQVyZDmjoS~iJkc7lC?m1?Nu4#(nr8ts6n1@8-qjsEf*DZ=Aa7YotEb<;%;f4_Ak`n>}AHH z7}*zp0Fn6~vW0W&>F5&N90r4sVgokVTiTRN1aJ&U=ghoxj|hKK2PlQ&%$;(G5qBah z&#xHY3`C#gApzJ~5RV5PPz3lp5dDv)@i%PxuP(*S`LSX-Zrt|ure=3p1B#%}l+j2? zLJLARIT28}N<9mKVlP+jw8nX$fggoC91jRt3`aMkH{IO_7*u@jbC`JU)4qpciKdSj zfD$sjPUsi0kFX&A+QLsW>7LjPysXTGg;Z1(dCtoWt2fy|BR1v!*xnLW98g{t#k812 z7;Tr)4KJPccb&WD(WcD-e=`>Dxw3Z$83L*wGRN74kL7MjO$7H}V9Aki|0Kw5GwB-^ zi;ikmH?d;zl14d~dUp&;V0+>J8TR>jtlrg{{WGMBb;uM(pPyBDjnZ+YD+x-vs=&@= zXmu)=Az+r+m(1J7IHt(N-9{=B4xgGU-Wg&s=cRYPBX~SxLZaT;E3Q;s9{cDQqyLMV3F;^(VVI8XzfZXsXKybt947 zXF`J*^Y`HUm7-#7%a+mrdRrycofnYL*w4%w$A3)y0Q1>0@rY52Iv{|zY4=nY`Ey?C z_I^hJe_JdPw)c;Vx2>R0!nctVr)XH+yPt0h1)zo_RE)Cfq`COVA- zF!BfUZ3&SAsw}71hoXMJzD5xNbXIFkceR}jBjCI!QU>!shf@EH9Yeq}#S3^9H{W@B zYQX4^Lf=pP7u}iV!dH3BD1annNNM*_zUeWlKE)D0SUZ}Q5R>fIyk$-q_xesfOSu+| zsk!<+w8~?*r*t;bd)%H%(Lm5{j8(tM@m$W5?BQ_{)UC8Iph$B;IwW?lFuN6j1p(kI zSA#JtDxX;sRgekbaAFH@_>l6scwEn&$SLW2nhx$Zs@(fGoV58oz5NB|a%3k|`83qt zc1Gc74v+(=!!@A!4ww&p#)sRLXKDe`hO5H$%|h3c%W5JkWUW5U8WgUyd$G!BgnM>F z;qO76Q^i^!O#`e*eEKmEVd3TB50~HNyb0sWf*y{_X&bL`$uWpGId7FCgfY#WE{O19 zG*oD+pg@{vtLP<`!LzZ4a;BaayfVZPAs5fsIIqm%6&SR5?hVh}#2bFeyvBtrC(Au; zTc{~4Uz$sxg&>L6xd<3(D179GmFhOjb0kBFyxK&WV8LHI#xGJ_e{~*vcMX!rBHIE>mSyrPR=b36K|u>x#tZ2?ibEqGJ{Ub-Tp3I8xKd zT!kBrF{KAe0ua77ey{ua{`Wi8gk%Qu9p~GPk)$HdTlo&*UbORTACa6jKSA|tuqrm} z^QdqkFEEyGF?G&og5{5xt>22aNj^r@s~T&7wq)#*AskPuaTbB~SKQsqCF5)*z99FPjh#$o559(27^qnP z6ep(QPXW7s<;fqxg|A64;_o%N%{3#O7%4Tck+&oux0oPF=v}G!ua2MvANbAKlOC)^ zXA{O;AFv|>c`L+2sTeIaOowdVUzW&I8u0Db@(1{Zk_B`Wu8f(y{UQ}hoOBF{PKex$r%dFiJsUM>{ zerWO4UXXSyr7OU`_~=+2S|IP3?4z5(GBbY zu*+ou>$61qP}l{sfLx{zJo5}q{iwGIxPHkfkfb+{=EfhQo-335H!U++^9=rH#OAoo_u{uxG`Y^-a$lvO?v`{J zIQWG*7fvwdF*d+?`1iE&%^;y~&BNM?GGXxcnZt7Pm67M>`?JP|eBOm7^xD=3gjaw4 zcWJLprN@v~MOTu!V7;aEAa>5aYJyqb#%gWeiv5{Agfc_*L))ji9j4olJho}yf0wu! z9HIcc)kZy`+ZgXD(zus?IG5`aW9os*3eBO$AHSin9`G%)D11bNW%^MQ%F8lE) z!pTzWYe;&0Om|6c0ivr@dBC7;43Z8`l`S7QozSi-heCzxEi#2ePs_A5D@?4Hqb@xlzsJ)Ig0YIp-B8kJpsgO z-%IK{$WK^D5b}q0_hAT3(uT6@pt`_$dT7L%(<+Tw$u(xTtT>Z0!-Q z-~JKi958!_Q-|r0;=wY^CQ$BYf0`UrLN8rsSR*i2y0%y}pxzbo0?NQK{TIeQV$$2O(Jn*@CQ_evyFRX&>8YB41d#V{nKs^r zRl9v<#W^Acl?}wynm9bR1%WOGnly}#igiHGOC6*n=USaNrVsktEqQV2*)n)66Iqw| zZu7Y!eVz_kPk!s2^GzMvs=bbO2Ezh2 z;p{}%`nW4S#l(UOsdgUzd?)WSzwO?xZ73> z`|LC|H`os&c7umOEI4?_&#^+^EGXm_)XP*_jVC+ZgtpH}>pq2C>7~An&D*=)sqJ8$2zoaR_=V=cXwtpPjofTb3dETWo(KxOp1aRD_5@7rJJmUivFWS@?&UEiBP zVrOCWVQOx9tW$&jO6~5?^kz0A^ITt#e~>fuxf?j)k~X-VDbk7wa@zNN4Wa1W%YHlP ze9CE#A!Ibxw`ie?YANU*tbfbdtTvMyDltXLZKg{f-Xb0y>I3`9D*Y*x_KN2`P(ZM@ zuW&5wgTOaHa&iud1a2Mr6c-kcNF-1miXfU+si7StWUQD0HoS^@%La?Bo?kYXV^|7aapc=i9C)@gQz0}_9ZAQX7Tsos!|no^gQn(P|7ThLt94p?iUMY70qf7MBq z0XZG)Kw%zsnp_Rmi!2aeR}X@;Q+Oi&bXUClWlLG4S`i-wT0s)!lWZ`%aDaqP7caBT8u~wphiU z!Hfm^HRS^dcKTO5V~NK4`B*3II@FV2S*TZmbks9lGFo=Os?8cEihw3`lH>Y`F`l)d zxUn9d!*Z4bj=x^D^cTJz5&t6c5|TtWUD}#;> zhGig`gX5i}J%L)gF9Cc_i2Z!%A%dZC`_B4_60F?1Q2dSielyr;VKgBjo+`w@04bu$NhwvZn-;pD9D5&kKOHODMBt zKntXF5rDlm1 znwK=5-y4-i@YEA%ly$DdFGWQg4M3M=0ZAHiELi^g9jkYDGq0yc>Zt)9sRT*eSWoC6T zMk~eF}=o=NT5pVP*5?<_*yE7f0aRL>woj4rog zFd6kB_~sj?*~?6)3XJ$@BW(Dk-LKno2VIv#Eu~&Ij_`kA*=L_5DJG}0!7CPLb@0J? zO1wJLo8;*a8o@fP;PDX7Z~JZoB(8poaJ*mF5d}h+HjPWu^e@?lj%NRsZ#ivRMRGHl zwRzK=7itScrX)$G#k7?%iJcPjK;hBmNvHuH1@;j3^GA@v0p-fWZ@Z|9%?QKvx}#|C zkvHfoPWA_`H8Xj@2En#^9^GH*^Vb>oM1|+tCV|C~hpOCjFbz)q-NqmtS=08r^dLWM zT~_wLeC0KKEuEg(@JgWIcijq0dg*l!i+dt4qzPP`Q2K&C(n8Juxs9YGwwoe$z0oTn zl1z~NDRn49b}%g}m9AR|$H5jq<&w1#BPRIvO$!RjZZW6T9^b;I2FRlo1xWS6{k#R{ z-em!aFG|F3vQRtg{J>U-UfbjLSNBao=egg1ZS(yLRLVrq!>{M}Vl;E5%t7GmB598L zzIP-T2r7MeqyrpWB`L*ltA+-+^Zx$i4CY(2G&KCYfrra(hL zf%($z(^$4gv%9XgY*w7%-PIQ7+n(=q%9C?`SFFN&9{*7Xx&hJ`BDf*O6pz}gXi@B+#hzgl?Tf79Q7!+KO z^DB9p_L#v+7z=>axupD`z$Nk+Bwvci(C)>Fd^}a$2$Y8X^&sKPS5f2dWFwQS(cF~Jp z!G0d*NK;)Mk>6!)3I5`C_0%=c3ZE^7_Z|f_#Fq;A2Qv(z4F_`jHJcs%h4EzW+|HMyx4@E0Si@{2HB~s_n zOK4Lk9zKCH$K(2lPwwN|WN_XyAKav+w*mrz%fZvSkGf_4hTfev_!cufCoYbJcpZ7TTcto3Ba6N+ec<_Y))i0FQR ze74vO63K+;0PWHp>k=GwDXmb6VpFEG_YKAEL@vB`eOav^6y{3>0#U!`0_V>zERn); zfG2Odf-xq`%783N1f#^eZ}bb?Szd|?KuQCQt*e|)mJ4|sl=!WQ^xAy(!b~56g4GEqbbIXuD4j-sQiFWk(u2&W<`e;(K-lyHNik0M-QQ z3%7juSD&7SEg)43(P~cbp?JYbFa~{4UP>UTNmN7p-5M$t-HdMmakigq1-d2%4A5s9 ziGUeKwtx4Mf8sd*>Qa`;ra&+S5b*mQfjG$hb0S$QF&xl|Toi!eD}h`Mxee0C{Wb{? zBO`m|*Y@GRsp>t;G9!IspNXG-+mv(#j`f*rJy=0rlifqWo5SephzvFH;6;V zDbhOO9{c>2x~dm2k|z%}2{p?J?p~uk{mdkfo39n`(-9%*B6*D#dhR9gJxD%O*x^B> z(LA4Wex25u84)yz{hG5q2 zggidxs2eEMSdBh^;;Zv0JC9d-*5CZd21Ya$`%C%VW%jnM z7{#~d^GD^E{_Ma`FJ(k4M0S|>_x+0S5A#LK&EWB{ZUV^X4rk{Evjw`I$R(XDpb@JM z0P;uoDyhe!3td896==JmY7*nT83D%uigOm!>_vIWpd=~n7=YmK?PUQ;rddF5*l7VG zL7>3d(RWTLk6$lw0X=2WJM|D0&x;>A1BfD=;Qyk`{)fesXhHM4vX7TYZC8XI)P{@6 zA8jw1>CN8&I$tAtjGC+9vyh5s_V+X52^k)9z2j9pL2mBNh7tLK65^WN#7>dnNAmdn1F zaq@4DJ9p$EGPR~%Q^r4)fy~$DxWu(c3`z~y0^~qIE&>53v~!%3K2 zr{8}WF&3`l6To<^P_4(XhwMIkZDg}9<9M}nG>?7Vi&4XZnF^4{&IqB7=~DdHMm>yD zB`>*K_bXH1yF)=ng16JgwK~KxTH0DC^*zfOF5N$3cVw^%Fn@lmcEf=i39a6>#>SY% zmXff^re@(@QCHZ<#QYJZv5+NsoutO-Q`0neZdU!2UAe;Nand`@>p&_za0;0_rDcUz z9d+5qD5$pnQ{6T(H)l(o<@m6rVQnb<+>mkP;JjSetsF$cJE zgqEYV`N~jp%mAxDe<1>mGh=D8w%?uiT{6&P1(eQWdpmyjeOt_CwJy^mv}Al9ihyF; z7{v+B{2TZOjm3t^q21-9cReUKr2>WTCY{0uIM*OWg!CY<{ISf6tRB_lOb&1bPyVIiUMrYxr@IR!M8`6erdr2} zXPa~}Z!TOLCSWFwpw%dEp7>Lyy08WdwKGBp*oIS#vP} z9DlIpIVaSL2|IrloA538nN#D+t4xfa4!U#nEIfHi-H?ru8Dd9%HF+P; z2v6Z^aWdKW$w4#SK0WzsMGQ-+i@?0Nr6f(+jcra6kZ!RmQRBnNU@Fxu(U~`GcxeCd4*Zj@in5kg9 z;m3k2-CF@(yDWvkd?h?fA%%IL4tkLmwnz&NlYDuyVi7UYMr3x5Q~+*h~M5P%5r6u#wD zn5wf(px}4J|K64Jc;tzX`4hY64nuD~;Jk`@Oz;xD4$+^ODl4Z|v;T^YHe79?V3IMB z100hy-{`|KpnhN8z_?k0qLHjO%hP{HLBC1^ehRFqC~PCx3kaRg?qJ)FcOwha%W_GN z{wDkZWTLN2`|h<@Py+KIO`sSpJ-BKInh)OFjL%E9F0CPfuT_il6#u!bVa+c_Y=D4f zk!;%+IY6(IdCBpaQk$PXt(j)vD>94m$$Lu5dcPJ@NiqZzh@` z*w@^fm(QmVDyY9$D>K8P12NFj@o!4|Y-u(uBJAd;=ncsRJ^Pf!>iQhq1IDd50Zjbq z%2{;Zft&rPx>ROmwdcCbz~fO#Anb80W)35d_4aF=oy#Z5jUO?~oxxBuFi84 z^N7$>?dQx8ceL8oR_;LoMsKe_{2=5s9RUUv=oQkA%t3W5bGuE&`;U=G7CjBUAfT_s zQ))~J?r+Mlz`$^HBwJDX5_PC!d)$7yve~N{=ziH%#aj4^ZcQdT<%?BwQjf45CGMr< zw#I((S-qpeC4njx?VDiG@Pf3!uNjV6`!;?6ApE2z&O1yjFhdZJULY&UR z1Ep}Tb{@M}?H!-?pPaSOL)foEFgG@3$)Xv2?N2RIH~^Gf4n9B0mv^(Vn|s1wgJ%H= zoQ7IlnpLpleo8p^DWeGl_Gz|0KWRu2Sp78AmHIp@SqD*%z}U>c^lz@1r1f~vK~#Kq zzxU<^yz@?+oHaR$duj{#QA&@#2A(bf=0H4UzR^MY>W_RE8&vV_Tlpl-2M91$GvvQz z1G6yji^N6vq(>Q+MV#B8o)Ra+K0sdd%IDk=>|WB)u5wMd&MW-T0Da9#ehr#NK-6rM z3i(U3FGN4KJ|GbAM0L~Y2wxtOi;2;^;Rx z5dw%L#JxGq7sS2#)~}bGhERO;mwb?YUV@Y*Vo?Ho@M0J*>3+Ql(#QiUq7`5E0zB~8 z_kW-GB^qTX<^PotT7`0eoKyNrh*F=rRvqfo9yi<6-X`$PVI473(X?18m-pebV2w2) z5=Akh$wCulw%<~xmG{z(<~B(bs*D=+n@Tmji`qL-&fI0jLb3RzwGeRPTlEME<_Sb6 ztVC)8sZSrfe40#{WF(1k+Bl7EyS`NXqpxE!tb5DWxUFFNcqyPo`JM&wj1nCdi1Uu3 zZK{uShO3E6U)`4vV6XnhAhUONNdx2~w`Q&^Zf29%^3YmFzB7}?1@T1=e3a?i?nJjG zRuCU!u8(xM)EN6W6E^sa8ME=xGHi|z4_ZfaCFGQV=zrwUAk>y_@BdO;j)|9QcU&OU zmg1Zm4I5mtyR`XXFjAYS3|i`qv$Y?Iif{HvgI06Or62>;`N3hxB^Ct^w0GC48G5U4 zBtbG9U$j|uV!Dr$Ybc!)_>8$@gjT;4$REZ1XdB*=q)da=i%U)d5q57M2A)&nG#~!h zPdx2E@_nQ+LAjF z1ciyCvAUa#PGuIQgy|@$i3E( z|6!&Z&9d0Cj<5fV_fF4wDvpb+Y<}Gg{d0IhS3^la60NW+>`f_hc7k2l6=USj21yNu z%%8k>JCO)zp_*n|wWXD(ePzM|6F201>QHj!+A3WYFp#IoOfv_Pa+#WZ+PJ5uDL4Bw8up%(gp0=Kcg&+y4MhyTVd3-NT*ZETH z&J|I|BEgOJ6hOq}KarBevw%VEQ(}Ux0QOZ;5T;O3fN$r~?g^?N`2U4zEkuTNx^W{F z#pslZKders5p%x90K{|XLXx1zR*K)lnx)iUU?WE9;t8~`egT%8qmHs3PFfE<;Y&u z%|QD`IV`L)A=%j$o7z&d$epSPM?3>5Bsl1Ew7h-rq8??FPwv))M14z5At?uj>pQ_Z zN9Z_AaP6Hb?!T9&=FxC-)DQrolZAEU&jeCt==Wik24YpbFqcX)HBj()Be$i}93wl(sm<0?Ug{VOLSgQ#@4RZN%r3%4h33cah0NrKNUF`YC zR;O-G4M}eJCm+XF#9}Rn1Zp8wdm=N5@}ZA(-j&78tndjKZRu+dmxd3Ywujiacj@$a z-wPkBx-%8eAdBWALz3d2BVfZ-FoOs+sz{@CQ^iu_a){ByU5P^9Xc3hRguRY?wgR}^ zVg~#3a*{13^H2>Ej3^j5Kd}I~2}D7cubtdb{-Wnc@LkzD4dRg5g|dJUXLA|Xmq#4Z zf@1)71g8(_UNILX?yUI7IM2Tx!N-12uiqxei`>t5`#SG*?nFZ~n8uFuCF0OTt7GvV zH8s%dI2f+EZaNWykF!;>N9YsIO>ICon;^~0@9)DOP1vf)URh}U5ld73x9LkVbw-Zl zp{Ocuo{QgH8xA8aSI%S8#x1oAU7+AOq2cu}>>1&!##O;B?u|3wJ2nx4F}fU1 z$Y-VueTPe}x%Bz}e3>u&gUTtT3Y2J?9)_|T-*&bce_Pw_D)cT`RUS-oJiQ1 zoau3+XII##_eD>3Xqq!CV)jE7XHi4VEs~_wtri`^L>P0?% zskuHIe=zZQa+U`2`D@=Cunojuxu~Gg&rwD*5wm_4maEWfielc#6``G}qhyUMz==U! zz_Sk&ihMmuYc1Ip!j4_hy-lQ39hC!uRmfI+hUSkq^iPR+>)#pHqf?@7Rav=6;2kw8 zqkW`h1FTNqpBbhqJpZ5OFEt`~pmp(mp+PS*n1vn4n1DX_HJ)YHW2c5fHBb4La|aV3MfeV}8NYc5?&;9xa!izq{M!Cpgbo%N zF;KAfZCqEyeKPo`<yjbCul!xeK= zU+@f;6=2}5{TgNq-!pUEpI+y>Sm&DACaxc6hCbI7)hTtaQi zMCyP()&&YvE!R1f?8@63jrsvujWhn&BzXzLX&|Nh4m^5Tn`VWl6hWoOwQ zvI+-=q6iJdMgr`{TnlnRsw2uP{Y2&t`+H3c<)05pOC1g7x>9k3=Eo-4y~8$%qyP66@M&jnSNL(~bd}WkX=EJ(4e@@6eC&R|eVpxK z=AN(PoUpa3?=1x9z>h3c?Kg20?FKH>2xcOG53)}yT`Uo0d=1T09QLd6 zBM-0fN8bjE^mv(*d0B$zaBH_d*NVOT!CBp^UpCLHw;RiF#IrrMedU#-!b7Q_nq}Wz znzY`pWsj9>(|m5c=E7`$dHs%xXL(*__AU1md#Sx+0S7o>|LX^cRfdKx=gt}Wi9Way zA}~E<)5j98%=xf_0*^}Xp?UOu`QYHzj^q diff --git a/.github/workflows/docker-push.yml b/.github/workflows/docker-push.yml index 5583b2471..dc580f9be 100644 --- a/.github/workflows/docker-push.yml +++ b/.github/workflows/docker-push.yml @@ -87,6 +87,9 @@ jobs: uses: docker/build-push-action@v5 with: push: true + build-args: | + VERSION=${{ env.VERSION }} + SHA=${{ github.sha }} labels: ${{ steps.meta.outputs.labels }} tags: | ${{ env.BASE_TAG }}:${{ env.VERSION }} diff --git a/.vscode/launch.json b/.vscode/launch.json index 11b7d2e2d..9f9708e36 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,26 +1,27 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Launch API", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/src/HappyCode.NetCoreBoilerplate.Api/bin/Debug/netcoreapp3.1/HappyCode.NetCoreBoilerplate.Api.dll", - "args": [], - "cwd": "${workspaceFolder}/src/HappyCode.NetCoreBoilerplate.Api", - "stopAtEntry": false, - "serverReadyAction": { - "action": "openExternally", - "pattern": "^\\s*Now listening on:\\s+(https?://\\S+)", - }, - "env": { - "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_URLS": "http://*:5000", - } - } - ] -} +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Debug API", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/src/HappyCode.NetCoreBoilerplate.Api/bin/Debug/net8.0/HappyCode.NetCoreBoilerplate.Api.dll", + "args": [], + "cwd": "${workspaceFolder}/src/HappyCode.NetCoreBoilerplate.Api", + "stopAtEntry": false, + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)", + "uriFormat": "%s/swagger" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "http://localhost:5000", + } + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index fb2c872e1..a19d84a07 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,7 +7,9 @@ "type": "process", "args": [ "build", - "${workspaceFolder}/src/HappyCode.NetCoreBoilerplate.Api/HappyCode.NetCoreBoilerplate.Api.csproj" + "${workspaceFolder}/src/HappyCode.NetCoreBoilerplate.Api/HappyCode.NetCoreBoilerplate.Api.csproj", + "-c", + "Debug", ], "problemMatcher": "$msCompile" } diff --git a/Directory.Build.props b/Directory.Build.props index 9f4186974..10d365a7e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ Łukasz Kurzyniec - Copyright © happy+code Łukasz Kurzyniec 2022 + Copyright © happy+code Łukasz Kurzyniec 2024 2.0.0 diff --git a/README.md b/README.md index 0acc64347..a15d3593f 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,7 @@ After successful start of the solution in any of above option, check useful endp * swagger - * health check - +* version - ### Standalone @@ -183,6 +184,10 @@ Generally it is totally up to you! But in case you do not have any plan, You can * Configurations * `Serilog` configuration place - [SerilogConfigurator.cs](src/HappyCode.NetCoreBoilerplate.Api/Infrastructure/Configurations/SerilogConfigurator.cs) * `Swagger` registration place - [SwaggerRegistration.cs](src/HappyCode.NetCoreBoilerplate.Api/Infrastructure/Registrations/SwaggerRegistration.cs) + * Feature flag documentation filter - [FeatureFlagSwaggerDocumentFilter.cs](src/HappyCode.NetCoreBoilerplate.Api/Infrastructure/Filters/FeatureFlagSwaggerDocumentFilter.cs) + * Security requirement operation filter - [SecurityRequirementSwaggerOperationFilter.cs](src/HappyCode.NetCoreBoilerplate.Api/Infrastructure/Filters/SecurityRequirementSwaggerOperationFilter.cs) +* Logging + * Custom enricher to have version properties in logs - [VersionEnricher.cs](src/HappyCode.NetCoreBoilerplate.Api/Infrastructure/Logging/VersionEnricher.cs) * Simple exemplary API controllers - [EmployeesController.cs](src/HappyCode.NetCoreBoilerplate.Api/Controllers/EmployeesController.cs), [CarsController.cs](src/HappyCode.NetCoreBoilerplate.Api/Controllers/CarsController.cs), [PingsController.cs](src/HappyCode.NetCoreBoilerplate.Api/Controllers/PingsController.cs) * Example of BackgroundService - [PingWebsiteBackgroundService.cs](src/HappyCode.NetCoreBoilerplate.Api/BackgroundServices/PingWebsiteBackgroundService.cs) @@ -199,6 +204,8 @@ Generally it is totally up to you! But in case you do not have any plan, You can * DbContexts * MySQL DbContext - [EmployeesContext.cs](src/HappyCode.NetCoreBoilerplate.Core/EmployeesContext.cs) * MsSQL DbContext - [CarsContext.cs](src/HappyCode.NetCoreBoilerplate.Core/CarsContext.cs) +* Providers + * Version provider - [VersionProvider.cs](src/HappyCode.NetCoreBoilerplate.Core/Providers/VersionProvider.cs) * Core registrations - [CoreRegistrations.cs](src/HappyCode.NetCoreBoilerplate.Core/Registrations/CoreRegistrations.cs) * Exemplary MySQL repository - [EmployeeRepository.cs](src/HappyCode.NetCoreBoilerplate.Core/Repositories/EmployeeRepository.cs) * Exemplary MsSQL service - [CarService.cs](src/HappyCode.NetCoreBoilerplate.Core/Services/CarService.cs) @@ -224,7 +231,8 @@ Generally it is totally up to you! But in case you do not have any plan, You can * Fixture with TestServer - [TestServerClientFixture.cs](test/HappyCode.NetCoreBoilerplate.Api.IntegrationTests/Infrastructure/TestServerClientFixture.cs) * TestStartup with InMemory databases - [TestStartup.cs](test/HappyCode.NetCoreBoilerplate.Api.IntegrationTests/Infrastructure/TestStartup.cs) * Simple data feeders - [EmployeeContextDataFeeder.cs](test/HappyCode.NetCoreBoilerplate.Api.IntegrationTests/Infrastructure/DataFeeders/EmployeeContextDataFeeder.cs), [CarsContextDataFeeder.cs](test/HappyCode.NetCoreBoilerplate.Api.IntegrationTests/Infrastructure/DataFeeders/CarsContextDataFeeder.cs) -* Exemplary tests - [EmployeesTests.cs](test/HappyCode.NetCoreBoilerplate.Api.IntegrationTests/EmployeesTests.cs), [CarsTests.cs](test/HappyCode.NetCoreBoilerplate.Api.IntegrationTests/CarsTests.cs) + * Fakes - [FakePingService.cs](test/HappyCode.NetCoreBoilerplate.Api.IntegrationTests/Infrastructure/Fakes/FakePingService.cs) +* Exemplary tests - [EmployeesTests.cs](test/HappyCode.NetCoreBoilerplate.Api.IntegrationTests/EmployeesTests.cs), [CarsTests.cs](test/HappyCode.NetCoreBoilerplate.Api.IntegrationTests/CarsTests.cs), [PingsTests.cs](test/HappyCode.NetCoreBoilerplate.Api.IntegrationTests/PingsTests.cs) ![HappyCode.NetCoreBoilerplate.Api.IntegrationTests](.assets/itests.png "HappyCode.NetCoreBoilerplate.Api.IntegrationTests") @@ -232,13 +240,18 @@ Generally it is totally up to you! But in case you do not have any plan, You can [HappyCode.NetCoreBoilerplate.Api.UnitTests](test/HappyCode.NetCoreBoilerplate.Api.UnitTests) -* Exemplary tests - [EmployeesControllerTests.cs](test/HappyCode.NetCoreBoilerplate.Api.UnitTests/Controllers/EmployeesControllerTests.cs) -* Unit tests of `ApiKeyAuthorizationFilter.cs` - [ApiKeyAuthorizationFilterTests.cs](test/HappyCode.NetCoreBoilerplate.Api.UnitTests/Infrastructure/Filters/ApiKeyAuthorizationFilterTests.cs) +* Exemplary tests - [EmployeesControllerTests.cs](test/HappyCode.NetCoreBoilerplate.Api.UnitTests/Controllers/EmployeesControllerTests.cs), [CarsControllerTests.cs](test/HappyCode.NetCoreBoilerplate.Api.UnitTests/Controllers/CarsControllerTests.cs), [PingsControllerTests.cs](test/HappyCode.NetCoreBoilerplate.Api.UnitTests/Controllers/PingsControllerTests.cs) +* API Infrastructure Unit tests + * [ApiKeyAuthorizationFilterTests.cs](test/HappyCode.NetCoreBoilerplate.Api.UnitTests/Infrastructure/Filters/ApiKeyAuthorizationFilterTests.cs) + * [ValidateModelStateFilterTests.cs](test/HappyCode.NetCoreBoilerplate.Api.UnitTests/Infrastructure/Filters/ValidateModelStateFilterTests.cs) + * [VersionEnricherTests.cs](test/HappyCode.NetCoreBoilerplate.Api.UnitTests/Infrastructure/Logging/VersionEnricherTests.cs) [HappyCode.NetCoreBoilerplate.Core.UnitTests](test/HappyCode.NetCoreBoilerplate.Core.UnitTests) * Extension methods to mock `DbSet` faster - [EnumerableExtensions.cs](test/HappyCode.NetCoreBoilerplate.Core.UnitTests/Extensions/EnumerableExtensions.cs) * Exemplary tests - [EmployeeRepositoryTests.cs](test/HappyCode.NetCoreBoilerplate.Core.UnitTests/Repositories/EmployeeRepositoryTests.cs), [CarServiceTests.cs](test/HappyCode.NetCoreBoilerplate.Core.UnitTests/Services/CarServiceTests.cs) +* Providers tests + * [VersionProviderTests.cs](test/HappyCode.NetCoreBoilerplate.Core.UnitTests/Providers/VersionProviderTests.cs) with [HappyCode.NetCoreBoilerplate.Core.UnitTests.runsettings](test/HappyCode.NetCoreBoilerplate.Core.UnitTests/HappyCode.NetCoreBoilerplate.Core.UnitTests.runsettings) ![HappyCode.NetCoreBoilerplate.Core.UnitTests](.assets/utests.png "HappyCode.NetCoreBoilerplate.Core.UnitTests") diff --git a/dockerfile b/dockerfile index 4b895c3e9..0afcf8f44 100644 --- a/dockerfile +++ b/dockerfile @@ -1,3 +1,6 @@ +ARG VERSION=2.0.0 +ARG SHA=none + FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base USER $APP_UID WORKDIR /app @@ -45,6 +48,9 @@ COPY --from=publish /app . ENV DOTNET_NOLOGO=true ENV DOTNET_CLI_TELEMETRY_OPTOUT=true +ENV HC_SHA=${SHA} +ENV HC_VERSION=${VERSION} + HEALTHCHECK --interval=5m --timeout=3s --start-period=10s --retries=1 \ CMD curl --fail http://localhost:8080/healthz/live || exit 1 diff --git a/src/HappyCode.NetCoreBoilerplate.Api/Infrastructure/Configurations/SerilogConfigurator.cs b/src/HappyCode.NetCoreBoilerplate.Api/Infrastructure/Configurations/SerilogConfigurator.cs index 4841757bf..3cee3f35e 100644 --- a/src/HappyCode.NetCoreBoilerplate.Api/Infrastructure/Configurations/SerilogConfigurator.cs +++ b/src/HappyCode.NetCoreBoilerplate.Api/Infrastructure/Configurations/SerilogConfigurator.cs @@ -1,3 +1,4 @@ +using HappyCode.NetCoreBoilerplate.Api.Infrastructure.Logging; using Microsoft.Extensions.Configuration; using Serilog; using Serilog.Core; @@ -11,6 +12,7 @@ public static Logger CreateLogger() var configuration = LoadAppConfiguration(); return new LoggerConfiguration() .ReadFrom.Configuration(configuration) + .Enrich.With(new VersionEnricher(new ())) .CreateLogger(); } diff --git a/src/HappyCode.NetCoreBoilerplate.Api/Infrastructure/Logging/VersionEnricher.cs b/src/HappyCode.NetCoreBoilerplate.Api/Infrastructure/Logging/VersionEnricher.cs new file mode 100644 index 000000000..c6577909a --- /dev/null +++ b/src/HappyCode.NetCoreBoilerplate.Api/Infrastructure/Logging/VersionEnricher.cs @@ -0,0 +1,23 @@ +using HappyCode.NetCoreBoilerplate.Core.Providers; +using Serilog.Core; +using Serilog.Events; + +namespace HappyCode.NetCoreBoilerplate.Api.Infrastructure.Logging; + +public class VersionEnricher : ILogEventEnricher +{ + private readonly VersionProvider _versionProvider; + + public VersionEnricher(VersionProvider versionProvider) + { + _versionProvider = versionProvider; + } + + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + foreach (var item in _versionProvider.VersionEntries) + { + logEvent.AddPropertyIfAbsent(new LogEventProperty(item.Key, new ScalarValue(item.Value))); + } + } +} diff --git a/src/HappyCode.NetCoreBoilerplate.Api/Properties/launchSettings.json b/src/HappyCode.NetCoreBoilerplate.Api/Properties/launchSettings.json index 113d07f1e..013ad0082 100644 --- a/src/HappyCode.NetCoreBoilerplate.Api/Properties/launchSettings.json +++ b/src/HappyCode.NetCoreBoilerplate.Api/Properties/launchSettings.json @@ -6,8 +6,10 @@ "launchUrl": "swagger", "environmentVariables": { "ASPNETCORE_URLS": "http://localhost:5000", - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "HC_SHA": "2a6ba3cac8416214cfa84eb1f9092f130427479f", + "HC_VERSION": "2.0.0" } } } -} \ No newline at end of file +} diff --git a/src/HappyCode.NetCoreBoilerplate.Api/Startup.cs b/src/HappyCode.NetCoreBoilerplate.Api/Startup.cs index f3a679ab2..7b042d044 100644 --- a/src/HappyCode.NetCoreBoilerplate.Api/Startup.cs +++ b/src/HappyCode.NetCoreBoilerplate.Api/Startup.cs @@ -5,12 +5,14 @@ using HappyCode.NetCoreBoilerplate.Api.Infrastructure.Registrations; using HappyCode.NetCoreBoilerplate.BooksModule; using HappyCode.NetCoreBoilerplate.Core; +using HappyCode.NetCoreBoilerplate.Core.Providers; using HappyCode.NetCoreBoilerplate.Core.Registrations; using HappyCode.NetCoreBoilerplate.Core.Settings; using HealthChecks.UI.Client; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -91,6 +93,9 @@ public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env) ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse, }).ShortCircuit(); + endpoints.MapGet("/version", (VersionProvider provider) => provider.VersionEntries) + .ExcludeFromDescription(); + endpoints.MapControllers(); endpoints.MapBooksModule(); }); diff --git a/src/HappyCode.NetCoreBoilerplate.Core/Providers/VersionProvider.cs b/src/HappyCode.NetCoreBoilerplate.Core/Providers/VersionProvider.cs new file mode 100644 index 000000000..01edd0532 --- /dev/null +++ b/src/HappyCode.NetCoreBoilerplate.Core/Providers/VersionProvider.cs @@ -0,0 +1,24 @@ +using System.Collections; +using System.Linq; + +namespace HappyCode.NetCoreBoilerplate.Core.Providers; + +public class VersionProvider +{ + private const string PREFIX = "HC_"; + + private readonly Lazy> _versionEntries = new(GetVersionEntries); + + private static Dictionary GetVersionEntries() + { + var variables = Environment.GetEnvironmentVariables() + .Cast() + .Where(x => x.Key.ToString().StartsWith(PREFIX)) + .ToDictionary( + x => x.Key.ToString().Remove(0, PREFIX.Length), + y => y.Value.ToString()); + return variables; + } + + public Dictionary VersionEntries => _versionEntries.Value; +} diff --git a/src/HappyCode.NetCoreBoilerplate.Core/Registrations/CoreRegistrations.cs b/src/HappyCode.NetCoreBoilerplate.Core/Registrations/CoreRegistrations.cs index 8090327a4..7566d3eb9 100644 --- a/src/HappyCode.NetCoreBoilerplate.Core/Registrations/CoreRegistrations.cs +++ b/src/HappyCode.NetCoreBoilerplate.Core/Registrations/CoreRegistrations.cs @@ -1,3 +1,4 @@ +using HappyCode.NetCoreBoilerplate.Core.Providers; using HappyCode.NetCoreBoilerplate.Core.Repositories; using HappyCode.NetCoreBoilerplate.Core.Services; using Microsoft.Extensions.DependencyInjection; @@ -10,6 +11,7 @@ public static IServiceCollection AddCoreComponents(this IServiceCollection servi { services.AddTransient(); services.AddScoped(); + services.AddSingleton(); return services; } diff --git a/test/HappyCode.NetCoreBoilerplate.Api.UnitTests/HappyCode.NetCoreBoilerplate.Api.UnitTests.csproj b/test/HappyCode.NetCoreBoilerplate.Api.UnitTests/HappyCode.NetCoreBoilerplate.Api.UnitTests.csproj index 494d46fa2..bb37c6d69 100644 --- a/test/HappyCode.NetCoreBoilerplate.Api.UnitTests/HappyCode.NetCoreBoilerplate.Api.UnitTests.csproj +++ b/test/HappyCode.NetCoreBoilerplate.Api.UnitTests/HappyCode.NetCoreBoilerplate.Api.UnitTests.csproj @@ -1,4 +1,8 @@ + + $(MSBuildProjectDirectory)\HappyCode.NetCoreBoilerplate.Api.UnitTests.runsettings + + diff --git a/test/HappyCode.NetCoreBoilerplate.Api.UnitTests/HappyCode.NetCoreBoilerplate.Api.UnitTests.runsettings b/test/HappyCode.NetCoreBoilerplate.Api.UnitTests/HappyCode.NetCoreBoilerplate.Api.UnitTests.runsettings new file mode 100644 index 000000000..3f297f311 --- /dev/null +++ b/test/HappyCode.NetCoreBoilerplate.Api.UnitTests/HappyCode.NetCoreBoilerplate.Api.UnitTests.runsettings @@ -0,0 +1,10 @@ + + + + + TEST_VALUE + 36b90293 + 9.9.9 + + + diff --git a/test/HappyCode.NetCoreBoilerplate.Api.UnitTests/Infrastructure/Logging/VersionEnricherTests.cs b/test/HappyCode.NetCoreBoilerplate.Api.UnitTests/Infrastructure/Logging/VersionEnricherTests.cs new file mode 100644 index 000000000..ecf1fc0b1 --- /dev/null +++ b/test/HappyCode.NetCoreBoilerplate.Api.UnitTests/Infrastructure/Logging/VersionEnricherTests.cs @@ -0,0 +1,38 @@ +using System; +using FluentAssertions; +using HappyCode.NetCoreBoilerplate.Api.Infrastructure.Logging; +using HappyCode.NetCoreBoilerplate.Core.Providers; +using Serilog.Events; +using Xunit; + +namespace HappyCode.NetCoreBoilerplate.Api.UnitTests.Infrastructure.Logging; + +public class VersionEnricherTests +{ + private readonly VersionEnricher _sut; + + public VersionEnricherTests() + { + _sut = new VersionEnricher(new VersionProvider()); + } + + [Fact] + public void Properties_should_be_available() + { + // Arrange + var logEvent = GetEmptyLogEvent(); + + // Act + _sut.Enrich(logEvent, null); + + // Assert + logEvent.Properties["SHA"].ToString().Should().Contain("36b90293"); + logEvent.Properties["VERSION"].ToString().Should().Contain("9.9.9"); + } + + private static LogEvent GetEmptyLogEvent() + { + return new LogEvent(DateTimeOffset.UtcNow, LogEventLevel.Verbose, null, + new MessageTemplate(Guid.NewGuid().ToString(), []), []); + } +} diff --git a/test/HappyCode.NetCoreBoilerplate.Core.UnitTests/HappyCode.NetCoreBoilerplate.Core.UnitTests.csproj b/test/HappyCode.NetCoreBoilerplate.Core.UnitTests/HappyCode.NetCoreBoilerplate.Core.UnitTests.csproj index 8e26e805e..c32eb0b3b 100644 --- a/test/HappyCode.NetCoreBoilerplate.Core.UnitTests/HappyCode.NetCoreBoilerplate.Core.UnitTests.csproj +++ b/test/HappyCode.NetCoreBoilerplate.Core.UnitTests/HappyCode.NetCoreBoilerplate.Core.UnitTests.csproj @@ -1,4 +1,8 @@ + + $(MSBuildProjectDirectory)\HappyCode.NetCoreBoilerplate.Core.UnitTests.runsettings + + diff --git a/test/HappyCode.NetCoreBoilerplate.Core.UnitTests/HappyCode.NetCoreBoilerplate.Core.UnitTests.runsettings b/test/HappyCode.NetCoreBoilerplate.Core.UnitTests/HappyCode.NetCoreBoilerplate.Core.UnitTests.runsettings new file mode 100644 index 000000000..3f297f311 --- /dev/null +++ b/test/HappyCode.NetCoreBoilerplate.Core.UnitTests/HappyCode.NetCoreBoilerplate.Core.UnitTests.runsettings @@ -0,0 +1,10 @@ + + + + + TEST_VALUE + 36b90293 + 9.9.9 + + + diff --git a/test/HappyCode.NetCoreBoilerplate.Core.UnitTests/Providers/VersionProviderTests.cs b/test/HappyCode.NetCoreBoilerplate.Core.UnitTests/Providers/VersionProviderTests.cs new file mode 100644 index 000000000..a973aee4f --- /dev/null +++ b/test/HappyCode.NetCoreBoilerplate.Core.UnitTests/Providers/VersionProviderTests.cs @@ -0,0 +1,27 @@ +using FluentAssertions; +using HappyCode.NetCoreBoilerplate.Core.Providers; +using Xunit; + +namespace HappyCode.NetCoreBoilerplate.Core.UnitTests.Providers; + +public class VersionProviderTests +{ + private readonly VersionProvider _provider; + + public VersionProviderTests() + { + _provider = new VersionProvider(); + } + + [Fact] + public void Provided_should_returns_expected_values() + { + // act + var result = _provider.VersionEntries; + + // assert + result.Should().NotContainKeys("TEST_ENV", "HC_SHA", "HC_VERSION"); + result.Should().ContainKeys("SHA", "VERSION"); + result.Should().HaveCount(2); + } +}