From 158555c544e07560198d30b97fd353dc969333a0 Mon Sep 17 00:00:00 2001 From: Dev-Yan Date: Thu, 16 Feb 2023 16:14:24 -0300 Subject: [PATCH 1/9] feat: model, migrations, repository, controllers and resume --- .gitignore | 2 + Back-endDeveloper.docx.pdf | Bin 0 -> 90829 bytes Controllers/book/BookController.cs | 86 ++++++++++++++ Controllers/category/CategoryController.cs | 32 ++++++ Controllers/user/UserController.cs | 87 ++++++++++++++ .../20230215175055_DoroBooks.Designer.cs | 108 ++++++++++++++++++ Migrations/20230215175055_DoroBooks.cs | 77 +++++++++++++ Migrations/DataContextModelSnapshot.cs | 104 +++++++++++++++++ Model/Book.cs | 30 +++++ Model/Category.cs | 18 +++ Model/Context/DataContext.cs | 22 ++++ Model/Login.cs | 8 ++ Model/Map/BookMap.cs | 14 +++ Model/Users.cs | 27 +++++ Program.cs | 20 ++++ Properties/launchSettings.json | 31 +++++ README.md | 96 +--------------- Repositories/book/BookRepository.cs | 104 +++++++++++++++++ Repositories/book/IBookRepository.cs | 16 +++ Repositories/category/CategoryRepository.cs | 44 +++++++ Repositories/category/ICategoryRepository.cs | 12 ++ Repositories/user/IUserRepository.cs | 14 +++ Repositories/user/UserRepository.cs | 102 +++++++++++++++++ Services/TokenService.cs | 45 ++++++++ Startup.cs | 87 ++++++++++++++ api.csproj | 21 ++++ appsettings.Development.json | 9 ++ appsettings.json | 18 +++ 28 files changed, 1140 insertions(+), 94 deletions(-) create mode 100644 .gitignore create mode 100644 Back-endDeveloper.docx.pdf create mode 100644 Controllers/book/BookController.cs create mode 100644 Controllers/category/CategoryController.cs create mode 100644 Controllers/user/UserController.cs create mode 100644 Migrations/20230215175055_DoroBooks.Designer.cs create mode 100644 Migrations/20230215175055_DoroBooks.cs create mode 100644 Migrations/DataContextModelSnapshot.cs create mode 100644 Model/Book.cs create mode 100644 Model/Category.cs create mode 100644 Model/Context/DataContext.cs create mode 100644 Model/Login.cs create mode 100644 Model/Map/BookMap.cs create mode 100644 Model/Users.cs create mode 100644 Program.cs create mode 100644 Properties/launchSettings.json create mode 100644 Repositories/book/BookRepository.cs create mode 100644 Repositories/book/IBookRepository.cs create mode 100644 Repositories/category/CategoryRepository.cs create mode 100644 Repositories/category/ICategoryRepository.cs create mode 100644 Repositories/user/IUserRepository.cs create mode 100644 Repositories/user/UserRepository.cs create mode 100644 Services/TokenService.cs create mode 100644 Startup.cs create mode 100644 api.csproj create mode 100644 appsettings.Development.json create mode 100644 appsettings.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cbbd0b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin/ +obj/ \ No newline at end of file diff --git a/Back-endDeveloper.docx.pdf b/Back-endDeveloper.docx.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6e08163f0d514ba1006acd1f31a7075837733c10 GIT binary patch literal 90829 zcmc$@1yCi;)~<;(?(VR0Xx!c1-Q5~@ciXr-G%k%a-be$DyKCdRackUl=xBzI>Y^_b0KI{POSy)&BQjU(6 z9|Of5&0GPh9|J91EL>;>1>rxce=W29X9hD901G^mr~-gV(b2`;#O^OyqB; ze|41?12Drg$;WUt8t+k5zSVCkp_Rh=YTp z+s7bxQ@4Mv<*glT;h7`>>>uMDT|PVq=rMncVK#tgQnfI11L(1`b1`ypu<&pIIJvnP z**Tb*Spe+JoQyoITwH7bE@l=+Zgw6X?vMT=fWH#{YZfy%Ye$EVX#h+bsxt6@4*(h~ zH#a9&UM40_Pftd>k7+H;tsNN69POE`9hkgL92guNEtx)4`;R2t|L>jWWM=#bfSC)x z$;HOV%KX=PHf|0^7G`#Ko__-3;Q8Nk-rmI8&drhc!_N;l-7TzLOw27jgf0Kx{u{%8 z@tf!W1HU;KnK`&PSpl3J+>AW`h|b2%#>mXh!t>$xM=Wgr4@I}McC&If{YUPPZOx!# z;_zQwX8F75|9%Q2KO+6u2TYQ7CTOk=m&3{pA9HnLA8HyP~dL=C%x5qr3 z7w^O~K$v?(*PH+S^ZKUIZ{rCOUbdsmPWc)+!WdP9Ym}k3as3R{l&L@*l+E)Tt%38v zjaQF~FW*ins!@Mv*5{Q>8p#bzb%jQ|Gpk{WmLDJ5Pp6=2K8K>qIiiuqqNX&Y>Nd@* z)hTMrR?jMg|JI_OFb};+QmxdKnq9C>>rt2#ucT?hS1PG=HBpuIMTg3vn$_?TYCY^A z{cSVQZUd>SKcq|2x&Hc+%9~~7^up`+{Hnfu06`8i-k}{V`prF z^@-o}A_#}{W@i(5^Uald-n@fV_aJYaa&(;1<7y*1k+(V3DoPe8nBF4ukeIGY(OxvK zzfDbQp-XXacP|XBbBxF*Jb(Yx(QDsuI^VBGGbzoI zx|VREXxDVsP%$(&V}*XN9@edo_$mBMKl>eD-FA(>G_93fr{Gu{?=$0l3)P4!n>t}& z=x@aRCbORZOlBE!@TYt>*>x&66&f>#rlid3Tl9bvl24zkdVz88Y84LY5#-Ilp!vtufAK}P=d+Mi$~1?UQzc1j_m=~ zCd7VBs!^7>WekR?B?$f#ev6pn1%h%dK;{9Xe4lgM9DOXQ2Nk;riu`A!W+^?a;6f#9 zLfKhq6Gq28a3E+Ova^r~w{?LahdEjXpOxCrc^f`^x&C)Vzrr_Gmi%Eh7MOqy8PzVmdbczBCBF*YB%FsM2gc<4)@h+AEt|zA-{}2L~cFlYmx$Fh1(#1iR9~1 zF&p_Pku(WM;OXmCmZu+YOlVx&5}G%rvG`cS&SuN_*IB7?ETN1&%LH$Uya1f*`*%gru8Y@+5%TK1er9fD3%n|`tmC(`{pr&aPlqt3Wc{N{9%LlWu&`t7f$%;MB+ODYH|$fvvtJM!xCCP zZxjDYQ{<~{9ph%1tBPuqv}ONdN&)!KvuAt<;vvh^alZVk}r~-_7E@` zF-SaBzeDh>eiO$9&872YjHh6fesZ7lLTJIuLsJ$Nqg(6kvS;y3103#Kls<02f zC9kqcVe8jn4!!awnv3R5-OI0qhUlm*0L$}-&K*X55gm^N2SzAkHxtjml7?tIDGWj? z<{S`<=_r7cQ|QkD^t_sd!73u9V4ZP&#GWnN_|D;#9UxCT7 ze_k#}y5{UpBzCqy*W6#4pdXD`tj#H4$jtrpk~gnEzihqdTh@09(UxxYtrA9E^#Gyv z6RAJ1r#>o6=Cuy5a*#4wqw`7-4m`x?#DcB<1SU-NrA&qH`O@P0#C9xS!t&4jB&Si| zk1QEu38Tg!WgslXxSOcR98OhwUa#+(dX6C!=fxcdfm;Iv)39=FmQdHO1oslbF#~6Q zpX{r?>vjZVLU1Vb@Vi*RM#oudNF!?6Y<^4C`?Kl-sMD(hOF%_KU<6bxPp8i7O;)>G ziEgbG8G^Wscl3(lqPOjY+lXa{)Ra_J(h@Px4DUa6r#^?=@rkBP9-u0N5Pic` zcx?!i`^l4ysZg4(or09bjVlE~1YQYwErDd|a{Qty$Xd<3nL>>mmGL zvMm~V+8f-G*vHwXhbx#qX3vrMi&lhA9Kt-<$cgq}wQr*vF$qq?QJVymtt1*c3N%7c zfX?AF_--ch4EtdBn8{XUCnxZ$iIYDb5u9rCgbm1}G!XmKY%v>Hr3dDN0Z83EbVW4m zcUZPA^{B?|cBLLI2H|nKEe#RV^Ty;!^T~Bk`SYT2Pzz^2u+lFO4^EsOPY7J?rPqDg zg&&{^0eTXXQYT0?RCdf`IK~&lDz)t~Qw;4ANdLK?Nbmvha3Ca7=)u z!4}F09MnMHUN!g-2JWAANSw=$RaL={^`r>mOMX1WUEnJG`H@O`sA@9biK-z@uiPnd zvn~xxNa=wZB4_>g65VR=r8X+F6?OP96xbxG$zBu3NStU=ve(l5gHImxstk;;II>$s z1QmFcocV^lC>SG8E@Qh%JGPNj3CNJI6^@7}x1(S!K9D-*^?xvQhlg~yVSGLX5>`4! zG-!5JlT7hmEo^1qz^sH)q8mE?nV0n1F_noVCx3CWq$I7ARW$# zWjx;QMBGs@sMv*)q3FzX9U~bVW~InloML1f$y9(uac(y`ttr*9dFLLd^6ufcC_Fho z`7~cptttKi5!jy|KMPj-Qt4>vPR2SDo$It*Ctp#rt2A4z+9bI?oZ(nW!$G=`Xuv5(V+acS zdn@clceqmX&ibEYEUsJ6${emm3<;zH@O&I&4X04BAspPL)13|;)dotw_J+p;x>ak( z5*X;s8Hg2&N0Y>hM35Ap$2KtSFj!d7-ghJ35xReQdI$4shi4))A#b{1G$45AGi7p! zP{%P|CHUw&rVua(_KzlVga%pnN$Vq{4E^Cgt=nBA z?$**HfOpa?D^J&;-#mM6dajL#ejGeqh??5^##M8I_&Kzm5#r#uD7 zb(unBoWX%@FojeETXR;U98Mf>`71pkaA{=U{@=PkknrqXgT72HJ+9>GY9nl`bD_F)F`1JVuOWPCWz5tN@?()Y*QfJdjo#&HmY`>V$-*(Y&uYo>W}^O-2=UpcCOQmd*N)JRj*Rpb$0LiQ@Y}oC*mC@ zyzbx^Et7hY4C)ktuoH<+UpflGDH?1;=~!$+_g!YKO$iSZ;{@^x2eL@ImD-ZR#U&ET zhZ6)GJI{A$ZmF7$q@7|e$0W{kiXK=}yznN7N|;)R4aZeEk*}LCUhD6<(0%2&uWpi8 zxmTIyD`RV12}aSd3DfC2gI-y#I?L0<|MXX`q8paQ%_nMef5zj@wWBqzQgQrR*d&4( zvGUUyW{o(Uu!_k?j)Q%T2Fd0ycqV2#x(B(|7Hq?BsJHW?+)$r)I&-44*@5Gz@-*+y zhW;|V<}p;AAxeeT1^yqgv0RYK3$V;DqCOZcxkQ*y6&CD_Q5?m$UtP3JvXhUPwreKZ zBuNxgi!ZuMR?uW40sh)yALS5_>b#;_UQ+n669 zgkTD3IH1_YEF<{CAeR40ut@|Gz3?iojDQ$nEW~qxXQ%B2Xyt(R;MR4BeK{Jd#j~DG zl&$Gha?X6F7U4-e=+7#$X(BH_h(dn&f-QUcv!^cps)*4)3}{3Tn)Cpe8j;wnft_uc^SE} zMJJpjM%BLXjWn;6DHtEFb%v1_0ENI+E|i$Qqfi}>$|2eU2}{e>gkXF_Mh3v zFCb&BIjNJ=f04Qa)+!T+wJ!%9iJ&a5&bPuKt(E=$;1~T;diY%_p%vvtj1I;#YsJON zcnyQlMd(twZ1L#?xZd-89s$>7{^{z}rPWhR6HFaT(Z7V=6k3lZMUn4&=P&9nKgdlm z@W8scrMw>D`QZxkSB9MiBiqJ0E5ZY#% z`S~;kk;n@)9mj|f5ZTitN|xF@4p-QUEJD7WaI!mZEc+{*Neda|R0!7g@|*q}T?P}o zha+8|Si)D=XpN&;lWN$XI+y;KTH2j%0>yidy6d1=*9XxJqX*?GzhXf(HMUaJwxjAB zeRHKi9CD4ivmTRQRy>=US&x$tNDge(pi=Zsag~s^n!~o)tV{FCQ=RGW<%s z#J;d6%P~#FLRV*ha}veq(sW4jg}t*|97|gKX$J>xI7W2Z_A+j@^y^E4zMowQ$JM@5 zGfiKYVj<{Fv(eL&6b`FDa7O4L zH%eOOST3L)LtMddjf`fX$MGeHM|mUYx5Qf+68yD6&8^-*L3*2EXD9In$ z`{`T4{2thIfpm*43DbVqsK%Po8yVZ0A%Wc;ZX~U>dv0ftAJMubrhuMjq+|DPwts;3 zGdu_T;Ghh_!E1HqonQc1A5z81M*SimlF?mm_F``ad8?~USPT;%r0mxV3(TXcc$TlR zOw=HKQ|>GRqSD0R{TOPgerwik`JGJtH{RV<&TAAyqRve`CPSgO>$9J73UVe?M0zWC zU#$D{@kbO0cqaUAUND>J)ef>HiK01olk5-D-MQ}%ZZI45J50SCBul^dwt^psUh;i! z^I%)8w@IS&Db2bm>nrnu|6V&dIVHel^R<#T+u0!i|71cPGH0^*!uwA@KRjQ8XxKJO zdDmwpqLYj<=HDWNF2wSe&uhK!;72pSnZF6Mf2S7yojPOT_}|M*uyC;b*Sv)8sskYx zqW@-X<^hC?cv2BX0gZ9(AO13qX%wrkU=nHtNAHk(llDVwt{#%vaoG_oK}=$DCCDRU zs%AFr{+*=H$+A`f&wdZjTdH>X$eVviIm)K;H#yc(j0A}`I(PQxgd$S-Q#f~vIgJ8@ z{P^2{5-BRBiT?T~@NyfmU%T9?g0GPwM>|Rl)tQzVau%T(>;@0Pr}8X4aS&R{{P;T6 z$5h?6Jg8noUZ4Aui%QTxhwCixoYQdGXKo)m_4%N0wQNb*VxMs$7YJ%6&nm$u$1Q{&!5?}x!XWbC~9 zENK9soZ;5i(j^#gTF#(%W@jAKL^VL1apln?g47j}PV})-r5PFGhaZLvcFUv`tLR|-67IP$gG!Nvh*G-)N<`Y(gB|ZIY3pK{b9EWI1e>@e8 zhexaQ8jVhi$ynUYhT8v&Ww4my&SW(OJ-AzF-iAclq#{>Ey^iFHy8%0?;VBOz-2;tx z1PhvuUE5bOxkO&48LEVfEyk<^M+Iz$JFG_o#q3k&4d%Q~n%Xj{ux5{VKbGW$c**NwL*sok*^hbr>R9ui><_TY%Q6ln_BiEjWeDvn4r;j^ zn9+O};<8SaLEqg4$Zz$UG`(~6?bm;_3frj&1&ypvJXP0T{z2NmtcETu#S~Va*32XW zt@&pyry1>uVr*1*Yr<|??K`rY%X(0zCeOxSOV^IBz!UtYny?Q%7*wREi}^?vb!>~; ze6pWe>P7_$5FJZ0t`RSAnL3TPcj*}RQo;2}A2z2Ps*c)qJhl#;WW6B?56C4J<{Np> z&$4Mc$pC*S7N9js7Ve&*9S1kZDb6L+`mCFDhy7IWQV>lh4_FWF2|kZ+me)(jMs57I z@~5uQn8*`{%bJ*L`M1PtKr!RoF=kch9zqyTtBT}NB|Jg%3kt%M;o)OM;eqGG(J440WHq3yv%zMS1S zdh$g)&AnK$xtVn|e-iWPD7BmVC$>r1L)?C>*9-V^fh${O-*B2m3|1f_nu}0nl^1%A zu(dN3>Ebmb@7DY6&eM}hNO7v@Lo}zkpEt)ry-h$U8e$gF5$>v5A6*!2EgUb@JTES5 z00Uqi3!;q(hMb@1a|3t9;Bz#OrO7*%)5WnVZ|Sfol-3Y3Cr&Pf;D#9z3U$~Z%+3oxpy@yo`EVM39}x)d9c*y;-@hZQ|M`ski%=+=Sbk)+RV`c{-CfKqT>qkr z54vNfX7PbM{sB8o>K0yZ045pxkB*}MK8yYPEb~D-B)r_D)Z9KYt-<9#wcI zDR#hLlB4=TF_0{|7!i)uJA!XOhg^M{)JpPKR^-}59?o$ z@|V%L0`&gE8&$wx3Ze@5%M?^U+QdF=S~z^P{*$r)Fsb$df6UyJO3Lh{wV(vhJ_9AQT|R~>i$4!~C=@$8+s8!zE7roo&B4a=Uto(roR_*p^F>9Q@8-rLeaWu|u^XD=Iire8u0B(pgF$`fyZZS7POSdmT=RP8J3I{Yd zR3ggt_lnhZ(t?NQjq&%l`wcF4wSxsi_v0Mi1NC)ZK4Gx#=pYyxg6uT5b1j5CNB~17 zZ-C!rIox~8O9B&wml+uUqI_3dv!ajo$|vFK%=hsdiNz@^n_!S=v|+WQ(T^*bIaaRTr0!WY5%J@W+` zJO*t~NUR2~khD8D?c|xKFf+{jSO?X<82SI%?keLR1sA6V`}y$Qd(O6&TayF!`Fl5O z*RM7Y=iCc)PpI%;Cf@HHOMwr^j!{b<862ZcV^SU zJ8H|gJc+>}7}7LA6Hc!4zC~qNEP!zv)(_#njn5+)K8!GJ2;Ukw1A9&UkHWY1z^>AI znvJtDXZ22xHx|_nQ`0r#?FmYC;k&@|t1S6SD1;b<#!O7rOP>kp?nJZ(rqp{5O1MD(NKqj}4JR*xvux}ol^DUu~-qMxrX z5Um$a8{K$I$uK7A&I>KcJjvw;uw6W^G5*?VtPh7IbTRU}hAS+qmwKDxk~T`ZvLwMg zLqAL_lvToeh<^AYo?8m@&-9 z3kM&h<$Nm{)>A3ewPl5^UNm*C+6lem?PVlthy>M2#EkhWdxLivGyUAQFytjul53B1 z5*jFZFE^7>Y}C8heajz40QAC*yF96{g<4V!F--4O=>1sK(#^<6^14&ib`;A!W`5Xf zHRg8QK2Wo*Q`#ahL@0v1*&y<^rpK3dL+)Y&W$45l1Uo55ws)+f?M3v(WW)V1q9DI^ z$CF^Z@8wLJ343nI3yUzr&%|gKcI^%0A>)1Rp~x(DP43;%;{7M@AoV-tE8zXr@`UV_ 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 GetBooks() + { + var book = await _bookRepository.Get(); + if (book == null) + return NotFound(); + + return Ok(book); + } + + [HttpGet("{Id}")] + [AllowAnonymous] + public async Task> GetBookById([FromRoute] int Id) + { + var book = await _bookRepository.Get(Id); + if (book == null) return NotFound(); + + return Ok(book); + + } + + [HttpPost] + [Authorize(Roles = "ADMIN,EMPLOYEE")] + public async Task> CreateBook([FromBody] Book book) + { + if (!ModelState.IsValid) + return BadRequest(ModelState); + + if (book.Id != 0 || book.Title != null) + return BadRequest(new { message = "book already exists" }); + + var createBook = await _bookRepository.Post(book); + return CreatedAtAction(nameof(GetBookById), new { id = createBook.Id }, createBook); + } + + [HttpPut("{Id}")] + [Authorize(Roles = "ADMIN,EMPLOYEE")] + public async Task UpdateBook([FromRoute] int Id, [FromBody] Book book) + { + if (Id != book.Id) + return BadRequest(StatusCode(400)); + + var existingBook = await _bookRepository.Get(Id); + if (existingBook == null) + return NotFound(); + + await _bookRepository.Put(book); + return NoContent(); + } + + + [HttpDelete("{Id}")] + [Authorize(Roles = "ADMIN")] + public async Task DeleteBook([FromRoute] int Id) + { + var book = await _bookRepository.Get(Id); + if (book == null) + return NotFound(); + + await _bookRepository.Delete(book.Id); + return NoContent(); + + } + } +} diff --git a/Controllers/category/CategoryController.cs b/Controllers/category/CategoryController.cs new file mode 100644 index 0000000..a7aabaa --- /dev/null +++ b/Controllers/category/CategoryController.cs @@ -0,0 +1,32 @@ +using api.Model; +using api.Repositories.category; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; + +namespace api.Controllers.category +{ + + [Route("api/[controller]")] + [ApiController] + public class CategoryController : ControllerBase + { + private readonly ICategoryRepository _categoryRepository; + + public CategoryController(ICategoryRepository categoryRepository) + { + _categoryRepository = categoryRepository; + } + + [HttpPost] + [Authorize(Roles = "ADMIN, EMPLOYEE")] + public async Task> CreateCategory([FromBody] Category category) + { + if (!ModelState.IsValid) + return BadRequest(ModelState); + + var createCategory = await _categoryRepository.Post(category); + return CreatedAtAction(nameof(CreateCategory), new { id = createCategory.Id }, createCategory); + } + } +} diff --git a/Controllers/user/UserController.cs b/Controllers/user/UserController.cs new file mode 100644 index 0000000..e4f3bd8 --- /dev/null +++ b/Controllers/user/UserController.cs @@ -0,0 +1,87 @@ +using api.Model; +using api.Repositories.user; +using api.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace api.Controllers.user +{ + [Route("api/[controller]")] + [ApiController] + public class UserController : ControllerBase + { + + private readonly IUserRepository _userRepository; + private readonly TokenService _tokenService; + + public UserController(IUserRepository userRepository, TokenService tokenService) + { + _userRepository = userRepository; + _tokenService = tokenService; + } + + [HttpPost("register")] + [AllowAnonymous] + public async Task Register([FromBody] Users users) + { + var existingUser = await _userRepository.Get(users.Id); + + if (existingUser != null) + { + return BadRequest(new { message = "user already exists" }); + } + + var user = new Users + { + Username = users.Username, + Password = users.Password, + Role = users.Role, + }; + + if (!ModelState.IsValid) + return BadRequest(ModelState); + + if (string.IsNullOrEmpty(users.Username)) + return StatusCode(400, new { message = "Username is null. please, enter a user to create your credentials." }); + + user.Password = ""; + + var token = _tokenService.GenerateToken(user); + + return Ok(new { user, token }); + } + + [HttpPost("login")] + [AllowAnonymous] + public async Task> Login([FromBody] Login login) + { + var user = await _userRepository.Get(login.Username, login.Password); + + if (user == null) + return StatusCode(404, new { message = "User not found!" }); + + if (string.IsNullOrEmpty(user.Username)) + return StatusCode(500, new { message = "Username is null or empty!" }); + + var token = _tokenService.GenerateToken(user); + + user.Password = ""; + + return Ok(new { user, token }); + } + + [HttpGet("user")] + [Authorize(Roles="ADMIN")] + public async Task>> GetUser() + { + var user = await _userRepository.Get(); + if (user == null) + return NotFound(); + + return Ok(user); + } + } +} diff --git a/Migrations/20230215175055_DoroBooks.Designer.cs b/Migrations/20230215175055_DoroBooks.Designer.cs new file mode 100644 index 0000000..cb774e7 --- /dev/null +++ b/Migrations/20230215175055_DoroBooks.Designer.cs @@ -0,0 +1,108 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using api.Model; + +namespace api.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20230215175055_DoroBooks")] + partial class DoroBooks + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.11") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("api.Model.Book", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Author") + .IsRequired() + .HasColumnType("varchar(100)"); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasColumnType("varchar(300)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("varchar(250)"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Books"); + }); + + modelBuilder.Entity("api.Model.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(250)"); + + b.HasKey("Id"); + + b.ToTable("Category"); + }); + + modelBuilder.Entity("api.Model.Users", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Password") + .IsRequired() + .HasColumnType("varchar(250)"); + + b.Property("Role") + .HasColumnType("nvarchar(max)"); + + b.Property("Username") + .IsRequired() + .HasColumnType("varchar(250)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("api.Model.Book", b => + { + b.HasOne("api.Model.Category", "Category") + .WithMany("Books") + .HasForeignKey("CategoryId"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("api.Model.Category", b => + { + b.Navigation("Books"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20230215175055_DoroBooks.cs b/Migrations/20230215175055_DoroBooks.cs new file mode 100644 index 0000000..347dfdf --- /dev/null +++ b/Migrations/20230215175055_DoroBooks.cs @@ -0,0 +1,77 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace api.Migrations +{ + public partial class DoroBooks : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Category", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "varchar(250)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Category", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Username = table.Column(type: "varchar(250)", nullable: false), + Password = table.Column(type: "varchar(250)", nullable: false), + Role = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Books", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Title = table.Column(type: "varchar(250)", nullable: false), + Description = table.Column(type: "varchar(300)", nullable: false), + Author = table.Column(type: "varchar(100)", nullable: false), + CategoryId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Books", x => x.Id); + table.ForeignKey( + name: "FK_Books_Category_CategoryId", + column: x => x.CategoryId, + principalTable: "Category", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_Books_CategoryId", + table: "Books", + column: "CategoryId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Books"); + + migrationBuilder.DropTable( + name: "Users"); + + migrationBuilder.DropTable( + name: "Category"); + } + } +} diff --git a/Migrations/DataContextModelSnapshot.cs b/Migrations/DataContextModelSnapshot.cs new file mode 100644 index 0000000..7244eca --- /dev/null +++ b/Migrations/DataContextModelSnapshot.cs @@ -0,0 +1,104 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using api.Model; + +namespace api.Migrations +{ + [DbContext(typeof(DataContext))] + partial class DataContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.11") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("api.Model.Book", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Author") + .IsRequired() + .HasColumnType("varchar(100)"); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasColumnType("varchar(300)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("varchar(250)"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Books"); + }); + + modelBuilder.Entity("api.Model.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(250)"); + + b.HasKey("Id"); + + b.ToTable("Category"); + }); + + modelBuilder.Entity("api.Model.Users", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Password") + .IsRequired() + .HasColumnType("varchar(250)"); + + b.Property("Role") + .HasColumnType("int"); + + b.Property("Username") + .IsRequired() + .HasColumnType("varchar(250)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("api.Model.Book", b => + { + b.HasOne("api.Model.Category", "Category") + .WithMany("Books") + .HasForeignKey("CategoryId"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("api.Model.Category", b => + { + b.Navigation("Books"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Model/Book.cs b/Model/Book.cs new file mode 100644 index 0000000..11cd207 --- /dev/null +++ b/Model/Book.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace api.Model +{ + public class Book + { + [Key] + public int Id { get; set; } + + + [Required(ErrorMessage = "{0} is required!")] + [Column(TypeName = "varchar(250)")] + public string Title { get; set; } + + + [Required(ErrorMessage = "{0} is required!")] + [Column(TypeName = "varchar(300)")] + public string Description { get; set; } + + + [Required(ErrorMessage = "{0} is required!")] + [Column(TypeName = "varchar(100)")] + public string Author { get; set; } + + public int? CategoryId { get; set; } + + public Category Category { get; set; } + } +} diff --git a/Model/Category.cs b/Model/Category.cs new file mode 100644 index 0000000..bb449a6 --- /dev/null +++ b/Model/Category.cs @@ -0,0 +1,18 @@ +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace api.Model +{ + public class Category + { + [Key] + public int Id { get; set; } + + [Required(ErrorMessage = "{0} is required!")] + [Column(TypeName = "varchar(250)")] + public string Name { get; set; } + public virtual List Books { get; set; } + } +} diff --git a/Model/Context/DataContext.cs b/Model/Context/DataContext.cs new file mode 100644 index 0000000..2d01bdc --- /dev/null +++ b/Model/Context/DataContext.cs @@ -0,0 +1,22 @@ +using api.Model.Map; +using Microsoft.EntityFrameworkCore; + +namespace api.Model +{ + public class DataContext : DbContext + { + public DataContext(DbContextOptions options) : base(options) + { } + + public DbSet Users { get; set; } + public DbSet Books { get; set; } + public DbSet Category { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new BookMap()); + + base.OnModelCreating(modelBuilder); + } + } +} diff --git a/Model/Login.cs b/Model/Login.cs new file mode 100644 index 0000000..26e02c6 --- /dev/null +++ b/Model/Login.cs @@ -0,0 +1,8 @@ +namespace api.Model +{ + public class Login + { + public string Username { get; set;} + public string Password { get; set;} + } +} diff --git a/Model/Map/BookMap.cs b/Model/Map/BookMap.cs new file mode 100644 index 0000000..7aa0e38 --- /dev/null +++ b/Model/Map/BookMap.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace api.Model.Map +{ + public class BookMap : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.Id); + builder.HasOne(x => x.Category); + } + } +} diff --git a/Model/Users.cs b/Model/Users.cs new file mode 100644 index 0000000..8ecb4d5 --- /dev/null +++ b/Model/Users.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace api.Model +{ + public class Users + { + [Key] + public int Id { get; set; } + + [Required(ErrorMessage = "{0} is required!")] + [Column(TypeName = "varchar(250)")] + public string Username { get; set; } + + [Required(ErrorMessage = "{0} is required!")] + [Column(TypeName = "varchar(250)")] + public string Password { get; set; } + + public Role? Role { get; set; } + } + + public enum Role + { + ADMIN, + EMPLOYEE + } +} diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..a007dea --- /dev/null +++ b/Program.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace api +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json new file mode 100644 index 0000000..f8b48f2 --- /dev/null +++ b/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:37589", + "sslPort": 44317 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "api": { + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/README.md b/README.md index b8775d1..9ffd722 100644 --- a/README.md +++ b/README.md @@ -1,97 +1,5 @@ ## Desafio para Back-end Developer na DoroTech - C# .NET -#### Requisitos Gerais: +Acesse o link e veja mais informações sobre o projeto (Setup, Modelagem de dados, Tasks e Steps) : -Uma livraria da cidade teve um aumento no número de seus exemplares e está com um problema para identificar todos os livros que possui em estoque. -Para ajudar a livraria foi solicitado a você desenvolver uma aplicação web para gerenciar estes exemplares. Requisitos: - - -* O sistema deverá mostrar todos os livros cadastrados ordenados de forma ascendente pelo nome. -* Ao persistir, validar se o livro já foi cadastrado. -* O sistema deverá permitir consultar, criar, editar e excluir um livro. -* Os livros devem ser persistidos em um banco de dados. -* Criar algum mecanismo de log de registro e de erro. - -#### Requisitos Técnicos: - -* Configurar o Swagger na aplicação (fundamental), pois será usado para testes. -* Incluir mecanismo de autenticação no Swagger, usando Token JWT (Bearer). -* Para a persistência dos dados deve ser utilizado o Entity Framework. -* Como banco de dados, pode ser usado MySQL, PostgreSQL ou SQL Server. -* Utilizar migrations ou Gerar Scripts e disponibilizá-los um uma pasta. -* Incluir git.ignore no repositório para não subir arquivos de compilação. - - -#### Observações: -* O sistema deverá ser desenvolvido na plataforma .NET com C#. - (preferêncialmente 5.0+, caso for usado outra versão, informar no pull-request) -* Deve conter autenticação com dois níveis de acesso, um administrador e um público, o usuário de nível - público não terá autenticação, ou seja, terá acesso livre a consulta de livros -* Atenção aos princípio do SOLID. -* Não é necessária a criação de front-end, o teste será feito pelo Swagger UI. - -#### Diferenciais do desafio: -* Aplicação das boas práticas do DDD, TDD, Design Patterns, SOLID e Clean Code. -* A modelagem dos dados não será fornecida, de propósito. Desejamos avaliar a sua capacidade de abstração. -* A API deverá realizar tratamento de entrada de dados e retornar códigos de erro quando aplicáveis. -* Criar massa de dados para que seja possível verificar o funcionamento das lógicas propostas. -* Incluir parâmetros de paginação e campos de filtro nos métodos de consulta (GET). -* Documentar, via código-fonte, os campos, parâmetros e dados de retorno da API para exibição no Swagger. - - -## Como deverá ser entregue: - - 1. Faça um fork deste repositório; - 2. Realize o teste; - 3. Adicione seu currículo na raiz do repositório; - 4. Envie-nos o PULL-REQUEST para que seja avaliado. - - - -## C# Back-end Challenge (English) - -#### General requirements: - -A bookstore in town has had an increase in the number of its copies and is having a problem identifying all the books it has in stock. -To help the bookstore, you were asked to develop a web application to manage these copies. Requirements: - - -* The system should show all registered books sorted in ascending order by name. -* When persisting, validate if the book has already been registered. -* The system should allow consulting, creating, editing and deleting books. -* Books must be persisted in a database. -* Create some logging and error logging mechanism. - -#### Technical requirements: - -* Configure Swagger in the application (fundamental), as it will be used for testing. -* Include authentication mechanism in Swagger, using JWT Token (Bearer). -* For data persistence, Entity Framework must be used. -* As a database, MySQL, PostgreSQL or SQL Server can be used. -* Use migrations or Generate Scripts and make them available in a folder. -* Include git.ignore in the repository to avoid uploading deployment files. - - -#### Comments: -* The system must be developed on the .NET platform with C#. -(preferably 5.0+, if another version is used, inform the pull-request) -* Must contain authentication with two levels of access, an administrator and a public, user level -public will not have authentication, that is, it will have free access to consult books -* Attention to the principles of SOLID. -* No front-end creation required, testing will be done by Swagger UI. - -#### Challenge differentials: -* Application of DDD, TDD, Design Patterns, SOLID and Clean Code best practices. -* Data modeling will not be provided on purpose. We wish to assess your capacity for abstraction. -* The API must perform data entry handling and return error codes when applicable. -* Create mass of data so that it is possible to verify the functioning of the proposed logics. -* Include pagination parameters and filter fields in query (GET) methods. -* Document, via source code, the API fields, parameters and return data for display in Swagger. - - -## How it should be delivered: - - 1. Fork this repository; - 2. Carry out the test; - 3. Add your CV to the repository root; - 4. Send us the PULL-REQUEST to be evaluated. +https://early-mustard-4f8.notion.site/Challenge-DoroTech-C-302bde63b4cc407398a5121ec657e807 \ No newline at end of file diff --git a/Repositories/book/BookRepository.cs b/Repositories/book/BookRepository.cs new file mode 100644 index 0000000..e426153 --- /dev/null +++ b/Repositories/book/BookRepository.cs @@ -0,0 +1,104 @@ +using api.Model; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Threading.Tasks; + +namespace api.Repositories.book +{ + public class BookRepository : IBookRepository + { + private readonly DataContext _context; + private readonly ILogger _logger; + + public BookRepository(DataContext context, ILogger logger) + { + _context = context; + _logger = logger; + } + + public async Task> Get() + { + return await _context.Books.OrderBy(b => b.Title).ToListAsync(); + } + + public async Task Get(int Id) + { + return await _context.Books.FindAsync(Id); + } + + public async Task Post(Book book) + { + if (string.IsNullOrWhiteSpace(book.Title)) + { + _logger.LogWarning("a book with empty title was not created"); + return null; + } + + _logger.LogInformation($"creating book with name{book.Title}"); + _context.Books.Add(book); + + try + { + await _context.SaveChangesAsync(); + _logger.LogInformation($"a book {book.Title} is created successfully. :)"); + } + catch (DbException ex) + { + _logger.LogError(ex, "error creating book."); + return null; + } + + return book; + } + + public async Task Put(Book book) + { + if (string.IsNullOrWhiteSpace(book.Title)) + { + _logger.LogWarning("a book with empty title was not updated"); + return; + } + + _logger.LogInformation($"updating book with name{book.Title}"); + _context.Entry(book).State = EntityState.Modified; + + try + { + await _context.SaveChangesAsync(); + _logger.LogInformation($"book with name{book.Title} updated"); + } + catch (DbException ex) + { + _logger.LogInformation(ex, $"erro updated book {book.Title}"); + return; + } + } + public async Task Delete(int Id) + { + var bookId = await _context.Books.FindAsync(Id); + + if (bookId == null) + { + _logger.LogWarning($"book not found"); + return; + } + + _logger.LogInformation($"deleting book..."); + _context.Books.Remove(bookId); + + try + { + await _context.SaveChangesAsync(); + _logger.LogInformation($"book removed"); + } + catch (DbException ex) + { + _logger.LogError(ex, $"error with remove book"); + return; + } + } + } +} diff --git a/Repositories/book/IBookRepository.cs b/Repositories/book/IBookRepository.cs new file mode 100644 index 0000000..8257183 --- /dev/null +++ b/Repositories/book/IBookRepository.cs @@ -0,0 +1,16 @@ +using api.Model; +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace api.Repositories.book +{ + public interface IBookRepository + { + Task> Get(); + Task Get(int Id); + Task Post(Book book); + Task Put(Book book); + Task Delete(int Id); + } +} diff --git a/Repositories/category/CategoryRepository.cs b/Repositories/category/CategoryRepository.cs new file mode 100644 index 0000000..58a2719 --- /dev/null +++ b/Repositories/category/CategoryRepository.cs @@ -0,0 +1,44 @@ +using api.Model; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace api.Repositories.category +{ + public class CategoryRepository : ICategoryRepository + { + private readonly DataContext _context; + private readonly ILogger _logger; + + public CategoryRepository(DataContext context, ILogger logger) + { + _context = context; + _logger = logger; + } + + public async Task Post(Category category) + { + if (string.IsNullOrWhiteSpace(category.Name)) + { + _logger.LogWarning("a category with empty name was not created."); + return null; + } + + _logger.LogInformation($"creating category with name {category.Name}"); + _context.Category.Add(category); + + try + { + await _context.SaveChangesAsync(); + _logger.LogInformation($"category {category.Name} created successfuly. :)"); + } + catch (DbUpdateException ex) + { + _logger.LogError(ex, $"error creating category with name{category.Name}"); + throw; + } + return category; + } + } +} diff --git a/Repositories/category/ICategoryRepository.cs b/Repositories/category/ICategoryRepository.cs new file mode 100644 index 0000000..95db723 --- /dev/null +++ b/Repositories/category/ICategoryRepository.cs @@ -0,0 +1,12 @@ +using api.Model; +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace api.Repositories.category +{ + public interface ICategoryRepository + { + Task Post(Category category); + } +} diff --git a/Repositories/user/IUserRepository.cs b/Repositories/user/IUserRepository.cs new file mode 100644 index 0000000..bb4e450 --- /dev/null +++ b/Repositories/user/IUserRepository.cs @@ -0,0 +1,14 @@ +using api.Model; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace api.Repositories.user +{ + public interface IUserRepository + { + Task Get(string username, string password); + Task Get(int Id); + Task> Get(); + Task Post(Users users); + } +} diff --git a/Repositories/user/UserRepository.cs b/Repositories/user/UserRepository.cs new file mode 100644 index 0000000..9b6401e --- /dev/null +++ b/Repositories/user/UserRepository.cs @@ -0,0 +1,102 @@ +using api.Model; +using Microsoft.EntityFrameworkCore; +using System.Text; +using System.Threading.Tasks; +using System.Security.Cryptography; +using Microsoft.Extensions.Logging; +using System.Collections.Generic; +using System.Linq; + +namespace api.Repositories.user +{ + public class UserRepository : IUserRepository + { + private readonly DataContext _context; + private readonly ILogger _logger; + + + public UserRepository(DataContext context, ILogger logger) + { + _context = context; + _logger = logger; + } + public static string HashPassword(string password) + { + using (SHA256 sha256Hash = SHA256.Create()) + { + // Converte a senha em um array de bytes. + byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(password)); + + // Converte o array de bytes em uma string hexadecimal. + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < bytes.Length; i++) + { + builder.Append(bytes[i].ToString("x2")); + } + return builder.ToString(); + } + } + public async Task Get(string username, string password) + { + _logger.LogInformation($"Getting user: {username}"); + + var hashedPassword = HashPassword(password); + + var user = await _context.Users.SingleOrDefaultAsync(u => u.Username == username && u.Password == hashedPassword); + + if (user == null) + return null; + + return new Users + { + Username = username, + Password = password + }; + } + + public async Task> Get() + { + return await _context.Users.OrderBy(b => b.Username).ToListAsync(); + } + + public async Task Get(int Id) + { + return await _context.Users.FindAsync(Id); + } + + public async Task Post(Users users) + { + _logger.LogInformation($"Create User: {users.Username}"); + + if (string.IsNullOrWhiteSpace(users.Username) || + string.IsNullOrWhiteSpace(users.Password) || + string.IsNullOrWhiteSpace(users.Role.ToString())) + { + _logger.LogWarning("credentials is not inserted!"); + return null; + } + + var user = new Users + { + Username = users.Username, + Password = HashPassword(users.Password) + }; + + _logger.LogInformation("creating user"); + _context.Users.Add(user); + + try + { + _logger.LogInformation("user created successfully!"); + await _context.SaveChangesAsync(); + } + catch (DbUpdateException ex) + { + _logger.LogError(ex, $"Error register user: {users.Username}"); + return null; + } + + return users; + } + } +} diff --git a/Services/TokenService.cs b/Services/TokenService.cs new file mode 100644 index 0000000..e94038d --- /dev/null +++ b/Services/TokenService.cs @@ -0,0 +1,45 @@ +using api.Model; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System; +using Microsoft.Extensions.Configuration; +using api.Repositories.user; + +namespace api.Services +{ + public class TokenService + { + private readonly IConfiguration _configuration; + + public TokenService(IConfiguration configuration) + { + _configuration = configuration; + } + + public string GenerateToken(Users users) + { + if (users == null) + throw new ArgumentNullException(nameof(users)); + + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(_configuration.GetSection("JWT_SECRET").Value); + + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(new Claim[] + { + new Claim(ClaimTypes.Name, users.Username), + new Claim(ClaimTypes.Role, users.Role.ToString()) + }), + + Expires = DateTime.UtcNow.AddHours(2), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + } +} diff --git a/Startup.cs b/Startup.cs new file mode 100644 index 0000000..dca55dc --- /dev/null +++ b/Startup.cs @@ -0,0 +1,87 @@ +using api.Model; +using api.Repositories.book; +using api.Repositories.category; +using api.Repositories.user; +using api.Services; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; +using System.Text; + +namespace api +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddLogging(builder => builder.AddConsole()); // Adicionando logger e um dos provedores de logger + services.AddLogging(); + services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("devConn"))); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddControllers(); + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "api", Version = "v1" }); + }); + + var key = Encoding.ASCII.GetBytes(Configuration["JWT_SECRET"]); + services.AddAuthentication(x => + { + x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(x => + { + x.RequireHttpsMetadata = false; + x.SaveToken = true; + x.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(key), + ValidateIssuer = false, + ValidateAudience = false, + }; + }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "api v1")); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/api.csproj b/api.csproj new file mode 100644 index 0000000..0b5730a --- /dev/null +++ b/api.csproj @@ -0,0 +1,21 @@ + + + + net5.0 + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + diff --git a/appsettings.Development.json b/appsettings.Development.json new file mode 100644 index 0000000..8983e0f --- /dev/null +++ b/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/appsettings.json b/appsettings.json new file mode 100644 index 0000000..2b0403e --- /dev/null +++ b/appsettings.json @@ -0,0 +1,18 @@ +{ + "AllowedHosts": "*", + "ConnectionStrings": { + "devConn": "Data Source=localhost;Initial Catalog=DoroBooks;Integrated Security=True" + }, + "JWT_SECRET": "aGcB6CzZsWx8VuY2", + "Logging": { + "LogLevel": { + "Default": "Information" + }, + "Console": { + "IncludeScopes": true, + "LogLevel": { + "Microsoft": "Warning" + } + } + } +} From 4c36250f59cfe48df12ebdab4a0a742c7d712ce1 Mon Sep 17 00:00:00 2001 From: Dev-Yan Date: Sat, 18 Feb 2023 07:17:12 -0300 Subject: [PATCH 2/9] feat: create api document and implement CORS to allow access any IP --- Controllers/book/BookController.cs | 26 +- Controllers/category/CategoryController.cs | 4 + Controllers/user/UserController.cs | 31 +- DoroBooks.json | 521 +++++++++++++++++++++ Program.cs | 2 + Startup.cs | 41 +- api.csproj | 2 + appsettings.json | 2 + 8 files changed, 619 insertions(+), 10 deletions(-) create mode 100644 DoroBooks.json diff --git a/Controllers/book/BookController.cs b/Controllers/book/BookController.cs index 441b28e..7a8a86d 100644 --- a/Controllers/book/BookController.cs +++ b/Controllers/book/BookController.cs @@ -18,6 +18,9 @@ public BookController(IBookRepository bookRepository) _bookRepository = bookRepository; } + /// + /// Get all books in asceding order. Any user can submit this request. + /// [HttpGet] [AllowAnonymous] public async Task>> GetBooks() @@ -29,6 +32,10 @@ public async Task>> GetBooks() return Ok(book); } + /// + /// Get book by Id. Any user can submit this request. + /// + /// [HttpGet("{Id}")] [AllowAnonymous] public async Task> GetBookById([FromRoute] int Id) @@ -40,6 +47,11 @@ public async Task> GetBookById([FromRoute] int Id) } + /// + /// Create book. Only users with ADMIN and EMPLOYEE privileges can make this request. + /// + /// + /// [HttpPost] [Authorize(Roles = "ADMIN,EMPLOYEE")] public async Task> CreateBook([FromBody] Book book) @@ -47,13 +59,18 @@ public async Task> CreateBook([FromBody] Book book) if (!ModelState.IsValid) return BadRequest(ModelState); - if (book.Id != 0 || book.Title != null) + if (book.Id != 0 || book.Title != null) return BadRequest(new { message = "book already exists" }); var createBook = await _bookRepository.Post(book); return CreatedAtAction(nameof(GetBookById), new { id = createBook.Id }, createBook); } + /// + /// Update Book. Only users with ADMIN and EMPLOYEE privileges can make this request. + /// + /// + /// [HttpPut("{Id}")] [Authorize(Roles = "ADMIN,EMPLOYEE")] public async Task UpdateBook([FromRoute] int Id, [FromBody] Book book) @@ -69,7 +86,10 @@ public async Task UpdateBook([FromRoute] int Id, [FromBody] Book b return NoContent(); } - + /// + /// Delete Books. Only users with ADMIN privileges can make this request. + /// + /// [HttpDelete("{Id}")] [Authorize(Roles = "ADMIN")] public async Task DeleteBook([FromRoute] int Id) @@ -83,4 +103,4 @@ public async Task DeleteBook([FromRoute] int Id) } } -} +} \ No newline at end of file diff --git a/Controllers/category/CategoryController.cs b/Controllers/category/CategoryController.cs index a7aabaa..d35462d 100644 --- a/Controllers/category/CategoryController.cs +++ b/Controllers/category/CategoryController.cs @@ -18,6 +18,10 @@ public CategoryController(ICategoryRepository categoryRepository) _categoryRepository = categoryRepository; } + /// + /// Create Category. Only users with ADMIN privileges can make this request. + /// + /// [HttpPost] [Authorize(Roles = "ADMIN, EMPLOYEE")] public async Task> CreateCategory([FromBody] Category category) diff --git a/Controllers/user/UserController.cs b/Controllers/user/UserController.cs index e4f3bd8..3b65855 100644 --- a/Controllers/user/UserController.cs +++ b/Controllers/user/UserController.cs @@ -23,6 +23,10 @@ public UserController(IUserRepository userRepository, TokenService tokenService) _tokenService = tokenService; } + /// + /// Register User. Any user can submit this request. + /// + /// [HttpPost("register")] [AllowAnonymous] public async Task Register([FromBody] Users users) @@ -54,6 +58,10 @@ public async Task Register([FromBody] Users users) return Ok(new { user, token }); } + /// + /// Login. Any user can submit this request. + /// + /// [HttpPost("login")] [AllowAnonymous] public async Task> Login([FromBody] Login login) @@ -73,8 +81,11 @@ public async Task> Login([FromBody] Login login) return Ok(new { user, token }); } + /// + /// Get all users. Only users with ADMIN privileges can make this request. + /// [HttpGet("user")] - [Authorize(Roles="ADMIN")] + [Authorize(Roles = "ADMIN")] public async Task>> GetUser() { var user = await _userRepository.Get(); @@ -83,5 +94,23 @@ public async Task>> GetUser() return Ok(user); } + + /// + /// Create users. Only users with ADMIN privileges can make this request + /// + /// + [HttpPost("create")] + [Authorize(Roles = "ADMIN")] + public async Task CreateUser(Users users) + { + if (!ModelState.IsValid) + return BadRequest(ModelState); + + if (users.Id != 0 || users.Username != null) + return BadRequest(new { message = "User already exists." }); + + var createUser = await _userRepository.Post(users); + return CreatedAtAction(nameof(CreateUser), createUser); + } } } diff --git a/DoroBooks.json b/DoroBooks.json new file mode 100644 index 0000000..f6dca92 --- /dev/null +++ b/DoroBooks.json @@ -0,0 +1,521 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "DoroBooks API", + "description": "Simple book API with JWT Authentication", + "contact": { + "name": "Yan Lima", + "email": "yanborges125@gmail.com" + }, + "version": "v1" + }, + "paths": { + "/api/Book": { + "get": { + "tags": [ + "Book" + ], + "summary": "Get all books in asceding order. Any user can submit this request.", + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Book" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Book" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Book" + } + } + } + } + } + } + }, + "post": { + "tags": [ + "Book" + ], + "summary": "Create book. Only users with ADMIN and EMPLOYEE privileges can make this request.", + "requestBody": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Book" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Book" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/Book" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/Book" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Book" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Book" + } + } + } + } + } + } + }, + "/api/Book/{Id}": { + "get": { + "tags": [ + "Book" + ], + "summary": "Get book by Id. Any user can submit this request.", + "parameters": [ + { + "name": "Id", + "in": "path", + "description": "", + "required": true, + "schema": { + "type": "integer", + "description": "", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/Book" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Book" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Book" + } + } + } + } + } + }, + "put": { + "tags": [ + "Book" + ], + "summary": "Update Book. Only users with ADMIN and EMPLOYEE privileges can make this request.", + "parameters": [ + { + "name": "Id", + "in": "path", + "description": "", + "required": true, + "schema": { + "type": "integer", + "description": "", + "format": "int32" + } + } + ], + "requestBody": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Book" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Book" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/Book" + } + } + } + }, + "responses": { + "200": { + "description": "Success" + } + } + }, + "delete": { + "tags": [ + "Book" + ], + "summary": "Delete Books. Only users with ADMIN privileges can make this request.", + "parameters": [ + { + "name": "Id", + "in": "path", + "description": "", + "required": true, + "schema": { + "type": "integer", + "description": "", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success" + } + } + } + }, + "/api/Category": { + "post": { + "tags": [ + "Category" + ], + "summary": "Create Category. Only users with ADMIN privileges can make this request.", + "requestBody": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Category" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Category" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/Category" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/Category" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Category" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Category" + } + } + } + } + } + } + }, + "/api/User/register": { + "post": { + "tags": [ + "User" + ], + "summary": "Register User. Any user can submit this request.", + "requestBody": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Users" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Users" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/Users" + } + } + } + }, + "responses": { + "200": { + "description": "Success" + } + } + } + }, + "/api/User/login": { + "post": { + "tags": [ + "User" + ], + "summary": "Login. Any user can submit this request.", + "requestBody": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Login" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Login" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/Login" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/Users" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Users" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Users" + } + } + } + } + } + } + }, + "/api/User/user": { + "get": { + "tags": [ + "User" + ], + "summary": "Get all users. Only users with ADMIN privileges can make this request.", + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Users" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Users" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Users" + } + } + } + } + } + } + } + }, + "/api/User/create": { + "post": { + "tags": [ + "User" + ], + "summary": "Create users. Only users with ADMIN privileges can make this request", + "requestBody": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Users" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Users" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/Users" + } + } + } + }, + "responses": { + "200": { + "description": "Success" + } + } + } + } + }, + "components": { + "schemas": { + "Category": { + "required": [ + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string" + }, + "books": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Book" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "Book": { + "required": [ + "author", + "description", + "title" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "categoryId": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "category": { + "$ref": "#/components/schemas/Category" + } + }, + "additionalProperties": false + }, + "Role": { + "enum": [ + 0, + 1 + ], + "type": "integer", + "format": "int32" + }, + "Users": { + "required": [ + "password", + "username" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "role": { + "$ref": "#/components/schemas/Role" + } + }, + "additionalProperties": false + }, + "Login": { + "type": "object", + "properties": { + "username": { + "type": "string", + "nullable": true + }, + "password": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + } + } + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs index a007dea..ad0f1c4 100644 --- a/Program.cs +++ b/Program.cs @@ -5,6 +5,7 @@ namespace api { public class Program { +#pragma warning disable CS1591 public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); @@ -17,4 +18,5 @@ public static IHostBuilder CreateHostBuilder(string[] args) => webBuilder.UseStartup(); }); } +#pragma warning disable CS1591 } diff --git a/Startup.cs b/Startup.cs index dca55dc..bcfb9df 100644 --- a/Startup.cs +++ b/Startup.cs @@ -12,7 +12,10 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models; +using System; +using System.IO; +using System.Reflection; using System.Text; namespace api @@ -29,6 +32,36 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + services.AddCors(options => + { + options.AddDefaultPolicy(builder => + { + builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader(); + }); + }); + + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo + { + Version = "v1", + Title = "DoroBooks API", + Description = "Simple book API with JWT Authentication", + Contact = new OpenApiContact + { + Name = Configuration.GetSection("CONTACT_NAME").Value, + Email = Configuration.GetSection("CONTACT_EMAIL").Value + } + }); + + // Set the comments path for the Swagger JSON and UI. + var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + c.IncludeXmlComments(xmlPath); + }); + services.AddLogging(builder => builder.AddConsole()); // Adicionando logger e um dos provedores de logger services.AddLogging(); services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("devConn"))); @@ -37,10 +70,6 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddControllers(); - services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "api", Version = "v1" }); - }); var key = Encoding.ASCII.GetBytes(Configuration["JWT_SECRET"]); services.AddAuthentication(x => @@ -64,6 +93,7 @@ public void ConfigureServices(IServiceCollection services) // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + app.UseCors(); if (env.IsDevelopment()) { @@ -73,7 +103,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) } app.UseHttpsRedirection(); - app.UseRouting(); app.UseAuthorization(); diff --git a/api.csproj b/api.csproj index 0b5730a..ba2b7c7 100644 --- a/api.csproj +++ b/api.csproj @@ -1,6 +1,8 @@ + true + $(NoWarn);1591 net5.0 diff --git a/appsettings.json b/appsettings.json index 2b0403e..c6a23d0 100644 --- a/appsettings.json +++ b/appsettings.json @@ -4,6 +4,8 @@ "devConn": "Data Source=localhost;Initial Catalog=DoroBooks;Integrated Security=True" }, "JWT_SECRET": "aGcB6CzZsWx8VuY2", + "CONTACT_NAME": "Yan Lima", + "CONTACT_EMAIL": "yanborges125@gmail.com", "Logging": { "LogLevel": { "Default": "Information" From 496912beaa3715e3ddd654e2f53a035f4ef9ed34 Mon Sep 17 00:00:00 2001 From: Dev-Yan Date: Sat, 18 Feb 2023 07:46:25 -0300 Subject: [PATCH 3/9] fix: add rule to include bearear token auth for endpoints --- Startup.cs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Startup.cs b/Startup.cs index bcfb9df..df38bbb 100644 --- a/Startup.cs +++ b/Startup.cs @@ -55,6 +55,31 @@ public void ConfigureServices(IServiceCollection services) Email = Configuration.GetSection("CONTACT_EMAIL").Value } }); + + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme() + { + Name = "Authorization", + Type = SecuritySchemeType.ApiKey, + Scheme = "Bearer", + BearerFormat = "JWT", + In = ParameterLocation.Header, + Description = "JWT Authorization header using the Bearer scheme" + }); + + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + new string[] {} + } + }); // Set the comments path for the Swagger JSON and UI. var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; From fe5bc0259a3c1217d2a56b154ee06736aa8b66af Mon Sep 17 00:00:00 2001 From: Dev-Yan Date: Mon, 20 Feb 2023 03:20:57 -0300 Subject: [PATCH 4/9] fix: user creation and auth adjusted --- Model/Users.cs | 9 +-------- Repositories/user/UserRepository.cs | 11 ++++------- Services/TokenService.cs | 8 ++------ Startup.cs | 1 + 4 files changed, 8 insertions(+), 21 deletions(-) diff --git a/Model/Users.cs b/Model/Users.cs index 8ecb4d5..9c4032f 100644 --- a/Model/Users.cs +++ b/Model/Users.cs @@ -15,13 +15,6 @@ public class Users [Required(ErrorMessage = "{0} is required!")] [Column(TypeName = "varchar(250)")] public string Password { get; set; } - - public Role? Role { get; set; } - } - - public enum Role - { - ADMIN, - EMPLOYEE + public string Role { get; set; } } } diff --git a/Repositories/user/UserRepository.cs b/Repositories/user/UserRepository.cs index 9b6401e..c2ce363 100644 --- a/Repositories/user/UserRepository.cs +++ b/Repositories/user/UserRepository.cs @@ -44,9 +44,6 @@ public async Task Get(string username, string password) var user = await _context.Users.SingleOrDefaultAsync(u => u.Username == username && u.Password == hashedPassword); - if (user == null) - return null; - return new Users { Username = username, @@ -69,8 +66,7 @@ public async Task Post(Users users) _logger.LogInformation($"Create User: {users.Username}"); if (string.IsNullOrWhiteSpace(users.Username) || - string.IsNullOrWhiteSpace(users.Password) || - string.IsNullOrWhiteSpace(users.Role.ToString())) + string.IsNullOrWhiteSpace(users.Password)) { _logger.LogWarning("credentials is not inserted!"); return null; @@ -79,7 +75,8 @@ public async Task Post(Users users) var user = new Users { Username = users.Username, - Password = HashPassword(users.Password) + Password = HashPassword(users.Password), + Role = users.Role }; _logger.LogInformation("creating user"); @@ -96,7 +93,7 @@ public async Task Post(Users users) return null; } - return users; + return user; } } } diff --git a/Services/TokenService.cs b/Services/TokenService.cs index e94038d..a0b9dcd 100644 --- a/Services/TokenService.cs +++ b/Services/TokenService.cs @@ -20,18 +20,14 @@ public TokenService(IConfiguration configuration) public string GenerateToken(Users users) { - if (users == null) - throw new ArgumentNullException(nameof(users)); - var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(_configuration.GetSection("JWT_SECRET").Value); - var tokenDescriptor = new SecurityTokenDescriptor { - Subject = new ClaimsIdentity(new Claim[] + Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, users.Username), - new Claim(ClaimTypes.Role, users.Role.ToString()) + new Claim(ClaimTypes.Role, users.Role), }), Expires = DateTime.UtcNow.AddHours(2), diff --git a/Startup.cs b/Startup.cs index df38bbb..02628aa 100644 --- a/Startup.cs +++ b/Startup.cs @@ -130,6 +130,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseHttpsRedirection(); app.UseRouting(); + app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => From a547e13689152b35913c70e3185fd1a0455bdd5a Mon Sep 17 00:00:00 2001 From: Dev-Yan Date: Mon, 20 Feb 2023 05:17:25 -0300 Subject: [PATCH 5/9] refactor: code optimization --- Controllers/user/UserController.cs | 24 +++--------------------- Repositories/user/UserRepository.cs | 5 +++-- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/Controllers/user/UserController.cs b/Controllers/user/UserController.cs index 3b65855..d37903c 100644 --- a/Controllers/user/UserController.cs +++ b/Controllers/user/UserController.cs @@ -9,7 +9,7 @@ namespace api.Controllers.user { - [Route("api/[controller]")] + [Route("api/")] [ApiController] public class UserController : ControllerBase { @@ -84,9 +84,9 @@ public async Task> Login([FromBody] Login login) /// /// Get all users. Only users with ADMIN privileges can make this request. /// - [HttpGet("user")] + [HttpGet("users")] [Authorize(Roles = "ADMIN")] - public async Task>> GetUser() + public async Task>> GetUsers() { var user = await _userRepository.Get(); if (user == null) @@ -94,23 +94,5 @@ public async Task>> GetUser() return Ok(user); } - - /// - /// Create users. Only users with ADMIN privileges can make this request - /// - /// - [HttpPost("create")] - [Authorize(Roles = "ADMIN")] - public async Task CreateUser(Users users) - { - if (!ModelState.IsValid) - return BadRequest(ModelState); - - if (users.Id != 0 || users.Username != null) - return BadRequest(new { message = "User already exists." }); - - var createUser = await _userRepository.Post(users); - return CreatedAtAction(nameof(CreateUser), createUser); - } } } diff --git a/Repositories/user/UserRepository.cs b/Repositories/user/UserRepository.cs index c2ce363..7053edd 100644 --- a/Repositories/user/UserRepository.cs +++ b/Repositories/user/UserRepository.cs @@ -47,13 +47,14 @@ public async Task Get(string username, string password) return new Users { Username = username, - Password = password + Password = password, + Role = user.Role }; } public async Task> Get() { - return await _context.Users.OrderBy(b => b.Username).ToListAsync(); + return await _context.Users.ToListAsync(); } public async Task Get(int Id) From e7cd5ae8662a9e68aa1812b1023871a62af2ed8e Mon Sep 17 00:00:00 2001 From: Dev-Yan Date: Thu, 23 Feb 2023 09:24:10 -0300 Subject: [PATCH 6/9] feat: include pagination --- Controllers/book/BookController.cs | 11 +++++----- Repositories/book/BookRepository.cs | 31 ++++++++++++++++++++++++++++ Repositories/book/IBookRepository.cs | 2 +- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/Controllers/book/BookController.cs b/Controllers/book/BookController.cs index 7a8a86d..3b19f9a 100644 --- a/Controllers/book/BookController.cs +++ b/Controllers/book/BookController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; namespace api.Controllers.book @@ -23,13 +24,14 @@ public BookController(IBookRepository bookRepository) /// [HttpGet] [AllowAnonymous] - public async Task>> GetBooks() + public async Task>> GetBooks(int pageNumber = 1, int pageSize = 10, string orderBy = "id", string search = "") { - var book = await _bookRepository.Get(); - if (book == null) + var books = await _bookRepository.Get(pageNumber, pageSize, orderBy, search); + + if (books == null || !books.Any()) return NotFound(); - return Ok(book); + return Ok(books); } /// @@ -44,7 +46,6 @@ public async Task> GetBookById([FromRoute] int Id) if (book == null) return NotFound(); return Ok(book); - } /// diff --git a/Repositories/book/BookRepository.cs b/Repositories/book/BookRepository.cs index e426153..cb50202 100644 --- a/Repositories/book/BookRepository.cs +++ b/Repositories/book/BookRepository.cs @@ -100,5 +100,36 @@ public async Task Delete(int Id) return; } } + + public async Task> Get(int pageNumber, int pageSize, string orderBy, string search) + { + var books = _context.Books.AsQueryable(); + + // Filtrar pelo campo search + if (!string.IsNullOrEmpty(search)) + { + books = books.Where(b => b.Title.ToLower().Contains(search.ToLower())); + } + + // Ordenar pelo campo orderBy + switch (orderBy?.ToLower()) + { + case "title": + books = books.OrderBy(b => b.Title); + break; + case "author": + books = books.OrderBy(b => b.Author); + break; + default: + books = books.OrderBy(b => b.Id); + break; + } + + // Paginação + var skip = (pageNumber - 1) * pageSize; + books = books.Skip(skip).Take(pageSize); + + return await books.ToListAsync(); + } } } diff --git a/Repositories/book/IBookRepository.cs b/Repositories/book/IBookRepository.cs index 8257183..d7c3833 100644 --- a/Repositories/book/IBookRepository.cs +++ b/Repositories/book/IBookRepository.cs @@ -7,7 +7,7 @@ namespace api.Repositories.book { public interface IBookRepository { - Task> Get(); + Task> Get(int pageNumber, int pageSize, string orderBy, string search); Task Get(int Id); Task Post(Book book); Task Put(Book book); From ab46922cab69c86ceb36376936d84f2a9d491f4a Mon Sep 17 00:00:00 2001 From: Dev-Yan Date: Thu, 23 Feb 2023 09:50:36 -0300 Subject: [PATCH 7/9] fix: ignore compilation files --- .gitignore | 9 ++++++++- README.md | 13 +++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index cbbd0b5..b4a33e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,9 @@ bin/ -obj/ \ No newline at end of file +obj/ +x64/ +x86/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +*.log +*.nupkg diff --git a/README.md b/README.md index 9ffd722..e885293 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,14 @@ ## Desafio para Back-end Developer na DoroTech - C# .NET -Acesse o link e veja mais informações sobre o projeto (Setup, Modelagem de dados, Tasks e Steps) : +## .gitignore +https://github.com/github/gitignore + +Aqui estão alguns arquivos de compilação ignorados: +[x] Arquivos binários: arquivos gerados durante a compilação, como DLLs e arquivos executáveis; +[x] Arquivos intermediários: arquivos temporários gerados durante a compilação, como o arquivo .obj; +[x] Arquivos NuGet: pacotes NuGet que são baixados automaticamente durante a compilação e restauração de pacotes; + +## Acesse o link e veja mais informações sobre o projeto (Setup, Modelagem de dados, Tasks e Steps) : + +https://early-mustard-4f8.notion.site/Challenge-DoroTech-C-302bde63b4cc407398a5121ec657e807 -https://early-mustard-4f8.notion.site/Challenge-DoroTech-C-302bde63b4cc407398a5121ec657e807 \ No newline at end of file From ba14682924beebc4460f3b81f03f509b7d9c3cd6 Mon Sep 17 00:00:00 2001 From: Yan Lima <71718541+Dev-Yan@users.noreply.github.com> Date: Thu, 23 Feb 2023 09:52:07 -0300 Subject: [PATCH 8/9] fix: ajust readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e885293..472ac33 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ https://github.com/github/gitignore Aqui estão alguns arquivos de compilação ignorados: -[x] Arquivos binários: arquivos gerados durante a compilação, como DLLs e arquivos executáveis; -[x] Arquivos intermediários: arquivos temporários gerados durante a compilação, como o arquivo .obj; -[x] Arquivos NuGet: pacotes NuGet que são baixados automaticamente durante a compilação e restauração de pacotes; +
[x] Arquivos binários: arquivos gerados durante a compilação, como DLLs e arquivos executáveis;
+
[x] Arquivos intermediários: arquivos temporários gerados durante a compilação, como o arquivo .obj;
+
[x] Arquivos NuGet: pacotes NuGet que são baixados automaticamente durante a compilação e restauração de pacotes.
## Acesse o link e veja mais informações sobre o projeto (Setup, Modelagem de dados, Tasks e Steps) : From 56a81a43defbd007edbd4538e6671c9d3486502a Mon Sep 17 00:00:00 2001 From: Dev-Yan Date: Fri, 24 Feb 2023 11:50:56 -0300 Subject: [PATCH 9/9] fix: ajust validation create book --- Controllers/book/BookController.cs | 8 ++++++-- Repositories/book/BookRepository.cs | 5 +++++ Repositories/book/IBookRepository.cs | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Controllers/book/BookController.cs b/Controllers/book/BookController.cs index 3b19f9a..19c3a54 100644 --- a/Controllers/book/BookController.cs +++ b/Controllers/book/BookController.cs @@ -60,8 +60,12 @@ public async Task> CreateBook([FromBody] Book book) if (!ModelState.IsValid) return BadRequest(ModelState); - if (book.Id != 0 || book.Title != null) - return BadRequest(new { message = "book already exists" }); + var existingBook = await _bookRepository.Get(book.Author, book.Title); + + if (existingBook != null) + { + return BadRequest(new { message = "A book with the same author and title already exists" }); + } var createBook = await _bookRepository.Post(book); return CreatedAtAction(nameof(GetBookById), new { id = createBook.Id }, createBook); diff --git a/Repositories/book/BookRepository.cs b/Repositories/book/BookRepository.cs index cb50202..612e5c1 100644 --- a/Repositories/book/BookRepository.cs +++ b/Repositories/book/BookRepository.cs @@ -131,5 +131,10 @@ public async Task> Get(int pageNumber, int pageSize, string or return await books.ToListAsync(); } + + public async Task Get(string author, string title) + { + return await _context.Books.SingleOrDefaultAsync(u => u.Author == author && u.Title == title); + } } } diff --git a/Repositories/book/IBookRepository.cs b/Repositories/book/IBookRepository.cs index d7c3833..d8ab0c3 100644 --- a/Repositories/book/IBookRepository.cs +++ b/Repositories/book/IBookRepository.cs @@ -9,6 +9,7 @@ public interface IBookRepository { Task> Get(int pageNumber, int pageSize, string orderBy, string search); Task Get(int Id); + Task Get(string author, string title); Task Post(Book book); Task Put(Book book); Task Delete(int Id);