├── .gitignore
├── Manager.sln
├── README.md
└── src
├── .vscode
├── launch.json
└── tasks.json
├── 1 - Manager.API
├── Controllers
│ ├── AuthController.cs
│ ├── BaseController.cs
│ └── UserController.cs
├── Manager.API.csproj
├── Middlewares
│ └── ExceptionHandlerMiddleware.cs
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Startup.cs
├── Token
│ ├── ITokenService.cs
│ └── TokenService.cs
├── Utilities
│ └── Responses.cs
├── ViewModels
│ ├── CreateUserViewModel.cs
│ ├── LoginViewModel.cs
│ ├── ResultViewModel.cs
│ └── UpdateUserViewModel.cs
├── appsettings.Development.json
└── appsettings.json
├── 2 - Manager.Domain
├── Entities
│ ├── Base.cs
│ └── User.cs
├── Manager.Domain.csproj
└── Validators
│ └── UserValidator.cs
├── 3 - Manager.Services
├── DTO
│ └── UserDTO.cs
├── Interfaces
│ └── IUserService.cs
├── Manager.Services.csproj
└── Services
│ └── UserService.cs
├── 4 - Manager.Infra
├── Context
│ └── ManagerContext.cs
├── Interfaces
│ ├── IBaseRepository.cs
│ └── IUserRepository.cs
├── Manager.Infra.csproj
├── Mappings
│ └── UserMap.cs
├── Migrations
│ ├── 20201231061725_InitialMigration.Designer.cs
│ ├── 20201231061725_InitialMigration.cs
│ ├── 20201231062142_UpdateNameLength.Designer.cs
│ ├── 20201231062142_UpdateNameLength.cs
│ ├── 20201231062353_UpdateNameLength2.Designer.cs
│ ├── 20201231062353_UpdateNameLength2.cs
│ ├── 20201231062529_UpdateNameLengthToLessCharacters.Designer.cs
│ ├── 20201231062529_UpdateNameLengthToLessCharacters.cs
│ ├── 20201231062551_UpdateNameLengthToLessCharacters2.Designer.cs
│ ├── 20201231062551_UpdateNameLengthToLessCharacters2.cs
│ ├── 20210301044804_UpdatePasswordLenght.Designer.cs
│ ├── 20210301044804_UpdatePasswordLenght.cs
│ └── ManagerContextModelSnapshot.cs
└── Repositories
│ ├── BaseRepository.cs
│ └── UserRepository.cs
├── 5 - Manager.Core
├── Communication
│ ├── Handlers
│ │ └── DomainNotificationHandler.cs
│ ├── Mediator
│ │ ├── Interfaces
│ │ │ └── IMediatorHandler.cs
│ │ └── MediatorHandler.cs
│ └── Messages
│ │ └── Notifications
│ │ ├── DomainNotification.cs
│ │ └── Notification.cs
├── Enum
│ └── DomainNotificationType.cs
├── Manager.Core.csproj
├── Structs
│ └── Optional.cs
└── Validations
│ └── Message
│ └── ErrorMessages.cs
└── 6 - Manager.Tests
├── Configurations
└── AutoMapper
│ └── AutoMapperConfiguration.cs
├── Fixtures
└── UserFixture.cs
├── Manager.Tests.csproj
└── Projects
└── Services
└── UserServiceTests.cs
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
--------------------------------------------------------------------------------
/Manager.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30907.101
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{03272CCF-C675-4EBE-A400-FD35018161A4}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Manager.API", "src\1 - Manager.API\Manager.API.csproj", "{18F7B356-2558-48E4-890C-468E8DAD7FC0}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Manager.Domain", "src\2 - Manager.Domain\Manager.Domain.csproj", "{08FBE022-9C70-4077-842C-D73FBF889EAC}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Manager.Services", "src\3 - Manager.Services\Manager.Services.csproj", "{24E50940-2185-4503-B57B-B263BA189626}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Manager.Infra", "src\4 - Manager.Infra\Manager.Infra.csproj", "{2FD66AB1-346C-437B-8367-7723D3A1F96A}"
15 | EndProject
16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Manager.Core", "src\5 - Manager.Core\Manager.Core.csproj", "{4A6F800D-DA39-490A-A678-009E50BFB85B}"
17 | EndProject
18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Manager.Tests", "src\6 - Manager.Tests\Manager.Tests.csproj", "{EF4E3F09-2708-4660-B030-3D776AE34126}"
19 | EndProject
20 | Global
21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
22 | Debug|Any CPU = Debug|Any CPU
23 | Debug|x64 = Debug|x64
24 | Debug|x86 = Debug|x86
25 | Release|Any CPU = Release|Any CPU
26 | Release|x64 = Release|x64
27 | Release|x86 = Release|x86
28 | EndGlobalSection
29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
30 | {18F7B356-2558-48E4-890C-468E8DAD7FC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {18F7B356-2558-48E4-890C-468E8DAD7FC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {18F7B356-2558-48E4-890C-468E8DAD7FC0}.Debug|x64.ActiveCfg = Debug|Any CPU
33 | {18F7B356-2558-48E4-890C-468E8DAD7FC0}.Debug|x64.Build.0 = Debug|Any CPU
34 | {18F7B356-2558-48E4-890C-468E8DAD7FC0}.Debug|x86.ActiveCfg = Debug|Any CPU
35 | {18F7B356-2558-48E4-890C-468E8DAD7FC0}.Debug|x86.Build.0 = Debug|Any CPU
36 | {18F7B356-2558-48E4-890C-468E8DAD7FC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {18F7B356-2558-48E4-890C-468E8DAD7FC0}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {18F7B356-2558-48E4-890C-468E8DAD7FC0}.Release|x64.ActiveCfg = Release|Any CPU
39 | {18F7B356-2558-48E4-890C-468E8DAD7FC0}.Release|x64.Build.0 = Release|Any CPU
40 | {18F7B356-2558-48E4-890C-468E8DAD7FC0}.Release|x86.ActiveCfg = Release|Any CPU
41 | {18F7B356-2558-48E4-890C-468E8DAD7FC0}.Release|x86.Build.0 = Release|Any CPU
42 | {08FBE022-9C70-4077-842C-D73FBF889EAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43 | {08FBE022-9C70-4077-842C-D73FBF889EAC}.Debug|Any CPU.Build.0 = Debug|Any CPU
44 | {08FBE022-9C70-4077-842C-D73FBF889EAC}.Debug|x64.ActiveCfg = Debug|Any CPU
45 | {08FBE022-9C70-4077-842C-D73FBF889EAC}.Debug|x64.Build.0 = Debug|Any CPU
46 | {08FBE022-9C70-4077-842C-D73FBF889EAC}.Debug|x86.ActiveCfg = Debug|Any CPU
47 | {08FBE022-9C70-4077-842C-D73FBF889EAC}.Debug|x86.Build.0 = Debug|Any CPU
48 | {08FBE022-9C70-4077-842C-D73FBF889EAC}.Release|Any CPU.ActiveCfg = Release|Any CPU
49 | {08FBE022-9C70-4077-842C-D73FBF889EAC}.Release|Any CPU.Build.0 = Release|Any CPU
50 | {08FBE022-9C70-4077-842C-D73FBF889EAC}.Release|x64.ActiveCfg = Release|Any CPU
51 | {08FBE022-9C70-4077-842C-D73FBF889EAC}.Release|x64.Build.0 = Release|Any CPU
52 | {08FBE022-9C70-4077-842C-D73FBF889EAC}.Release|x86.ActiveCfg = Release|Any CPU
53 | {08FBE022-9C70-4077-842C-D73FBF889EAC}.Release|x86.Build.0 = Release|Any CPU
54 | {24E50940-2185-4503-B57B-B263BA189626}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
55 | {24E50940-2185-4503-B57B-B263BA189626}.Debug|Any CPU.Build.0 = Debug|Any CPU
56 | {24E50940-2185-4503-B57B-B263BA189626}.Debug|x64.ActiveCfg = Debug|Any CPU
57 | {24E50940-2185-4503-B57B-B263BA189626}.Debug|x64.Build.0 = Debug|Any CPU
58 | {24E50940-2185-4503-B57B-B263BA189626}.Debug|x86.ActiveCfg = Debug|Any CPU
59 | {24E50940-2185-4503-B57B-B263BA189626}.Debug|x86.Build.0 = Debug|Any CPU
60 | {24E50940-2185-4503-B57B-B263BA189626}.Release|Any CPU.ActiveCfg = Release|Any CPU
61 | {24E50940-2185-4503-B57B-B263BA189626}.Release|Any CPU.Build.0 = Release|Any CPU
62 | {24E50940-2185-4503-B57B-B263BA189626}.Release|x64.ActiveCfg = Release|Any CPU
63 | {24E50940-2185-4503-B57B-B263BA189626}.Release|x64.Build.0 = Release|Any CPU
64 | {24E50940-2185-4503-B57B-B263BA189626}.Release|x86.ActiveCfg = Release|Any CPU
65 | {24E50940-2185-4503-B57B-B263BA189626}.Release|x86.Build.0 = Release|Any CPU
66 | {2FD66AB1-346C-437B-8367-7723D3A1F96A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
67 | {2FD66AB1-346C-437B-8367-7723D3A1F96A}.Debug|Any CPU.Build.0 = Debug|Any CPU
68 | {2FD66AB1-346C-437B-8367-7723D3A1F96A}.Debug|x64.ActiveCfg = Debug|Any CPU
69 | {2FD66AB1-346C-437B-8367-7723D3A1F96A}.Debug|x64.Build.0 = Debug|Any CPU
70 | {2FD66AB1-346C-437B-8367-7723D3A1F96A}.Debug|x86.ActiveCfg = Debug|Any CPU
71 | {2FD66AB1-346C-437B-8367-7723D3A1F96A}.Debug|x86.Build.0 = Debug|Any CPU
72 | {2FD66AB1-346C-437B-8367-7723D3A1F96A}.Release|Any CPU.ActiveCfg = Release|Any CPU
73 | {2FD66AB1-346C-437B-8367-7723D3A1F96A}.Release|Any CPU.Build.0 = Release|Any CPU
74 | {2FD66AB1-346C-437B-8367-7723D3A1F96A}.Release|x64.ActiveCfg = Release|Any CPU
75 | {2FD66AB1-346C-437B-8367-7723D3A1F96A}.Release|x64.Build.0 = Release|Any CPU
76 | {2FD66AB1-346C-437B-8367-7723D3A1F96A}.Release|x86.ActiveCfg = Release|Any CPU
77 | {2FD66AB1-346C-437B-8367-7723D3A1F96A}.Release|x86.Build.0 = Release|Any CPU
78 | {4A6F800D-DA39-490A-A678-009E50BFB85B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
79 | {4A6F800D-DA39-490A-A678-009E50BFB85B}.Debug|Any CPU.Build.0 = Debug|Any CPU
80 | {4A6F800D-DA39-490A-A678-009E50BFB85B}.Debug|x64.ActiveCfg = Debug|Any CPU
81 | {4A6F800D-DA39-490A-A678-009E50BFB85B}.Debug|x64.Build.0 = Debug|Any CPU
82 | {4A6F800D-DA39-490A-A678-009E50BFB85B}.Debug|x86.ActiveCfg = Debug|Any CPU
83 | {4A6F800D-DA39-490A-A678-009E50BFB85B}.Debug|x86.Build.0 = Debug|Any CPU
84 | {4A6F800D-DA39-490A-A678-009E50BFB85B}.Release|Any CPU.ActiveCfg = Release|Any CPU
85 | {4A6F800D-DA39-490A-A678-009E50BFB85B}.Release|Any CPU.Build.0 = Release|Any CPU
86 | {4A6F800D-DA39-490A-A678-009E50BFB85B}.Release|x64.ActiveCfg = Release|Any CPU
87 | {4A6F800D-DA39-490A-A678-009E50BFB85B}.Release|x64.Build.0 = Release|Any CPU
88 | {4A6F800D-DA39-490A-A678-009E50BFB85B}.Release|x86.ActiveCfg = Release|Any CPU
89 | {4A6F800D-DA39-490A-A678-009E50BFB85B}.Release|x86.Build.0 = Release|Any CPU
90 | {EF4E3F09-2708-4660-B030-3D776AE34126}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
91 | {EF4E3F09-2708-4660-B030-3D776AE34126}.Debug|Any CPU.Build.0 = Debug|Any CPU
92 | {EF4E3F09-2708-4660-B030-3D776AE34126}.Debug|x64.ActiveCfg = Debug|Any CPU
93 | {EF4E3F09-2708-4660-B030-3D776AE34126}.Debug|x64.Build.0 = Debug|Any CPU
94 | {EF4E3F09-2708-4660-B030-3D776AE34126}.Debug|x86.ActiveCfg = Debug|Any CPU
95 | {EF4E3F09-2708-4660-B030-3D776AE34126}.Debug|x86.Build.0 = Debug|Any CPU
96 | {EF4E3F09-2708-4660-B030-3D776AE34126}.Release|Any CPU.ActiveCfg = Release|Any CPU
97 | {EF4E3F09-2708-4660-B030-3D776AE34126}.Release|Any CPU.Build.0 = Release|Any CPU
98 | {EF4E3F09-2708-4660-B030-3D776AE34126}.Release|x64.ActiveCfg = Release|Any CPU
99 | {EF4E3F09-2708-4660-B030-3D776AE34126}.Release|x64.Build.0 = Release|Any CPU
100 | {EF4E3F09-2708-4660-B030-3D776AE34126}.Release|x86.ActiveCfg = Release|Any CPU
101 | {EF4E3F09-2708-4660-B030-3D776AE34126}.Release|x86.Build.0 = Release|Any CPU
102 | EndGlobalSection
103 | GlobalSection(SolutionProperties) = preSolution
104 | HideSolutionNode = FALSE
105 | EndGlobalSection
106 | GlobalSection(NestedProjects) = preSolution
107 | {18F7B356-2558-48E4-890C-468E8DAD7FC0} = {03272CCF-C675-4EBE-A400-FD35018161A4}
108 | {08FBE022-9C70-4077-842C-D73FBF889EAC} = {03272CCF-C675-4EBE-A400-FD35018161A4}
109 | {24E50940-2185-4503-B57B-B263BA189626} = {03272CCF-C675-4EBE-A400-FD35018161A4}
110 | {2FD66AB1-346C-437B-8367-7723D3A1F96A} = {03272CCF-C675-4EBE-A400-FD35018161A4}
111 | {4A6F800D-DA39-490A-A678-009E50BFB85B} = {03272CCF-C675-4EBE-A400-FD35018161A4}
112 | {EF4E3F09-2708-4660-B030-3D776AE34126} = {03272CCF-C675-4EBE-A400-FD35018161A4}
113 | EndGlobalSection
114 | GlobalSection(ExtensibilityGlobals) = postSolution
115 | SolutionGuid = {66A726EC-31B2-4972-B9C7-3410F8F3292B}
116 | EndGlobalSection
117 | EndGlobal
118 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ManagerAPI
2 |
Projeto criado na série de vídeos onde eu ensino a construir uma API Rest utilizando .NET 6, EF Core, e princiaplmente boas práticas de desenvolvimento e arquitetura!
3 |
4 | Aulas:
5 |
6 |
7 | - #0 - Introdução
8 | - #1 - Definindo a Estrutura do Projeto
9 | - #2 - Modelando Nossas Entidades
10 | - #3.1- Iniciando a Camada de Infraestrutura e o Repository Pattern
11 | - #3.2 - Finalizando a Camada de infraestrutura
12 | - #4 - Construindo Nossa Camada de Serviço
13 | - #5.1 - Criando Nossa Camada de API
14 | - #5.2 - Adicionando JWT a Nossa API
15 | - #6 - Encerramento
16 | - #7 - [BONUS] Aumentando a Segurança da API
17 | - #8 - Iniciando nosso banco de dados no Azure!
18 | - #8.1 - Configurando o Azure Key Vault e realizando o deploy
19 | - #9 - Adicionando Testes Unitários
20 | - #10 - Migrando o Projeto Para .NET 6!
21 | - #11 - Refatorando Métodos e Implementando Hash
22 |
23 |
24 |
25 |
26 |
27 | Para poder rodar o projeto você precisa configurar algumas variaveis de ambiente
28 |
29 | Iniciar os segredos de usuários
30 |
31 | dotnet user-secrets init
32 |
33 |
34 |
35 | Configurar a string de conexão ao banco de dados
36 |
37 |
38 | dotnet user-secrets set "ConnectionStrings:USER_MANAGER" "[STRING CONNECTION]"
39 |
40 |
41 |
42 | Configurar dados de autenticação (JWT)
43 |
44 |
45 | dotnet user-secrets set "Jwt:Key" "[JWT CRYPTOGRAPHY KEY]"
46 | dotnet user-secrets set "Jwt:Login" "[JWT LOGIN]"
47 | dotnet user-secrets set "Jwt:Password" "[JWT PASSWORD]"
48 |
49 |
50 |
51 | Por fim você configura a chave de criptografia da aplicação
52 |
53 |
54 | dotnet user-secrets set "Cryptography" "[CHAVE DE CRIPTOGRAFIA DA APLICAÇÃO]"
55 |
56 |
57 |
58 |
59 | Alguns comandos que podem ser úteis :)
60 |
61 |
62 | Listar todas os segredos de usuário da aplicação.
63 |
64 |
65 | dotnet user-secrets list
66 |
67 |
68 |
69 | Deletar um segredo de usuário da aplicação.
70 |
71 |
72 | dotnet user-secrets remove "[CHAVE]"
73 |
74 |
75 |
76 |
77 | 2022©
78 |
--------------------------------------------------------------------------------
/src/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (web)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | "program": "${workspaceFolder}/1 - Manager.API/bin/Debug/net5.0/Manager.API.dll",
13 | "args": [],
14 | "cwd": "${workspaceFolder}/1 - Manager.API",
15 | "stopAtEntry": false,
16 | "serverReadyAction": {
17 | "action": "openExternally",
18 | "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
19 | },
20 | "env": {
21 | "ASPNETCORE_ENVIRONMENT": "Development"
22 | },
23 | "sourceFileMap": {
24 | "/Views": "${workspaceFolder}/Views"
25 | }
26 | },
27 | {
28 | "name": ".NET Core Attach",
29 | "type": "coreclr",
30 | "request": "attach",
31 | "processId": "${command:pickProcess}"
32 | }
33 | ]
34 | }
--------------------------------------------------------------------------------
/src/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/1 - Manager.API/Manager.API.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/1 - Manager.API/Manager.API.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "${workspaceFolder}/1 - Manager.API/Manager.API.csproj",
36 | "/property:GenerateFullPaths=true",
37 | "/consoleloggerparameters:NoSummary"
38 | ],
39 | "problemMatcher": "$msCompile"
40 | }
41 | ]
42 | }
--------------------------------------------------------------------------------
/src/1 - Manager.API/Controllers/AuthController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Manager.API.Token;
3 | using Manager.API.Utilities;
4 | using Manager.API.ViewModes;
5 | using Manager.Core.Communication.Messages.Notifications;
6 | using MediatR;
7 | using Microsoft.AspNetCore.Mvc;
8 | using Microsoft.Extensions.Configuration;
9 |
10 | namespace Manager.API.Controllers
11 | {
12 | [ApiController]
13 | public class AuthController : BaseController
14 | {
15 | private readonly IConfiguration _configuration;
16 | private readonly ITokenService _tokenService;
17 |
18 | public AuthController(
19 | IConfiguration configuration,
20 | ITokenService tokenService,
21 | INotificationHandler domainNotificationHandler)
22 | : base(domainNotificationHandler)
23 | {
24 | _configuration = configuration;
25 | _tokenService = tokenService;
26 | }
27 |
28 | [HttpPost]
29 | [Route("/api/v1/auth/login")]
30 | public IActionResult Login([FromBody] LoginViewModel loginViewModel)
31 | {
32 | var tokenLogin = _configuration["Jwt:Login"];
33 | var tokenPassword = _configuration["Jwt:Password"];
34 |
35 | if (loginViewModel.Login == tokenLogin && loginViewModel.Password == tokenPassword)
36 | return Ok(new ResultViewModel
37 | {
38 | Message = "Usuário autenticado com sucesso!",
39 | Success = true,
40 | Data = new
41 | {
42 | Token = _tokenService.GenerateToken(),
43 | TokenExpires = DateTime.UtcNow.AddHours(int.Parse(_configuration["Jwt:HoursToExpire"]))
44 | }
45 | });
46 | else
47 | return StatusCode(401, Responses.UnauthorizedErrorMessage());
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/src/1 - Manager.API/Controllers/BaseController.cs:
--------------------------------------------------------------------------------
1 | using Manager.API.ViewModes;
2 | using Manager.Core.Communication.Handlers;
3 | using Manager.Core.Communication.Messages.Notifications;
4 | using Manager.Core.Enum;
5 | using MediatR;
6 | using Microsoft.AspNetCore.Mvc;
7 | using System.Linq;
8 |
9 | namespace Manager.API.Controllers
10 | {
11 | [ApiController]
12 | public abstract class BaseController : ControllerBase
13 | {
14 | private readonly DomainNotificationHandler _domainNotificationHandler;
15 |
16 | protected BaseController(
17 | INotificationHandler domainNotificationHandler)
18 | {
19 | _domainNotificationHandler = domainNotificationHandler as DomainNotificationHandler;
20 | }
21 |
22 | protected bool HasNotifications()
23 | => _domainNotificationHandler.HasNotifications();
24 |
25 | protected ObjectResult Created(dynamic responseObject)
26 | => StatusCode(201, responseObject);
27 |
28 | protected ObjectResult Result()
29 | {
30 | var notification = _domainNotificationHandler
31 | .Notifications
32 | .FirstOrDefault();
33 |
34 | return StatusCode(GetStatusCodeByNotificationType(notification.Type),
35 | new ResultViewModel
36 | {
37 | Message = notification.Message,
38 | Success = false,
39 | Data = new { }
40 | });
41 | }
42 |
43 | private int GetStatusCodeByNotificationType(DomainNotificationType errorType)
44 | {
45 | return errorType switch
46 | {
47 | //Conflict
48 | DomainNotificationType.UserAlreadyExists
49 | => 409,
50 |
51 | //Unprocessable Entity
52 | DomainNotificationType.UserInvalid
53 | => 422,
54 |
55 | //Not Found
56 | DomainNotificationType.UserNotFound
57 | => 404,
58 |
59 | (_) => 500,
60 | };
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/1 - Manager.API/Controllers/UserController.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Manager.API.ViewModes;
3 | using Microsoft.AspNetCore.Mvc;
4 | using Manager.Services.Interfaces;
5 | using AutoMapper;
6 | using Manager.Services.DTO;
7 | using Microsoft.AspNetCore.Authorization;
8 | using MediatR;
9 | using Manager.Core.Communication.Messages.Notifications;
10 |
11 | namespace Manager.API.Controllers
12 | {
13 |
14 | [ApiController]
15 | public class UserController : BaseController
16 | {
17 |
18 | private readonly IMapper _mapper;
19 | private readonly IUserService _userService;
20 |
21 | public UserController(
22 | IMapper mapper,
23 | IUserService userService,
24 | INotificationHandler domainNotificationHandler)
25 | : base(domainNotificationHandler)
26 | {
27 | _mapper = mapper;
28 | _userService = userService;
29 | }
30 |
31 | [HttpPost]
32 | [Authorize]
33 | [Route("/api/v1/users/create")]
34 | public async Task CreateAsync([FromBody] CreateUserViewModel userViewModel)
35 | {
36 | var userDTO = _mapper.Map(userViewModel);
37 | var userCreated = await _userService.CreateAsync(userDTO);
38 |
39 | if (HasNotifications())
40 | return Result();
41 |
42 | return Ok(new ResultViewModel
43 | {
44 | Message = "Usuário criado com sucesso!",
45 | Success = true,
46 | Data = userCreated.Value
47 | });
48 | }
49 |
50 | [HttpPut]
51 | [Authorize]
52 | [Route("/api/v1/users/update")]
53 | public async Task UpdateAsync([FromBody] UpdateUserViewModel userViewModel)
54 | {
55 | var userDTO = _mapper.Map(userViewModel);
56 | var userUpdated = await _userService.UpdateAsync(userDTO);
57 |
58 | if (HasNotifications())
59 | return Result();
60 |
61 | return Ok(new ResultViewModel
62 | {
63 | Message = "Usuário atualizado com sucesso!",
64 | Success = true,
65 | Data = userUpdated.Value
66 | });
67 | }
68 |
69 | [HttpDelete]
70 | [Authorize]
71 | [Route("/api/v1/users/remove/{id}")]
72 | public async Task RemoveAsync(long id)
73 | {
74 | await _userService.RemoveAsync(id);
75 |
76 | if (HasNotifications())
77 | return Result();
78 |
79 | return Ok(new ResultViewModel
80 | {
81 | Message = "Usuário removido com sucesso!",
82 | Success = true,
83 | Data = null
84 | });
85 | }
86 |
87 | [HttpGet]
88 | [Authorize]
89 | [Route("/api/v1/users/get/{id}")]
90 | public async Task GetAsync(long id)
91 | {
92 | var user = await _userService.GetAsync(id);
93 |
94 | if (HasNotifications())
95 | return Result();
96 |
97 | if (!user.HasValue)
98 | return Ok(new ResultViewModel
99 | {
100 | Message = "Nenhum usuário foi encontrado com o ID informado.",
101 | Success = true,
102 | Data = user.Value
103 | });
104 |
105 | return Ok(new ResultViewModel
106 | {
107 | Message = "Usuário encontrado com sucesso!",
108 | Success = true,
109 | Data = user.Value
110 | });
111 | }
112 |
113 |
114 | [HttpGet]
115 | [Authorize]
116 | [Route("/api/v1/users/get-all")]
117 | public async Task GetAsync()
118 | {
119 | var allUsers = await _userService.GetAllAsync();
120 |
121 | if (HasNotifications())
122 | return Result();
123 |
124 | return Ok(new ResultViewModel
125 | {
126 | Message = "Usuários encontrados com sucesso!",
127 | Success = true,
128 | Data = allUsers.Value
129 | });
130 | }
131 |
132 |
133 | [HttpGet]
134 | [Authorize]
135 | [Route("/api/v1/users/get-by-email")]
136 | public async Task GetByEmailAsync([FromQuery] string email)
137 | {
138 | var user = await _userService.GetByEmailAsync(email);
139 |
140 | if (HasNotifications())
141 | return Result();
142 |
143 | if (!user.HasValue)
144 | return Ok(new ResultViewModel
145 | {
146 | Message = "Nenhum usuário foi encontrado com o email informado.",
147 | Success = true,
148 | Data = user.Value
149 | });
150 |
151 | return Ok(new ResultViewModel
152 | {
153 | Message = "Usuário encontrado com sucesso!",
154 | Success = true,
155 | Data = user.Value
156 | });
157 | }
158 |
159 | [HttpGet]
160 | [Authorize]
161 | [Route("/api/v1/users/search-by-name")]
162 | public async Task SearchByNameAsync([FromQuery] string name)
163 | {
164 | var allUsers = await _userService.SearchByNameAsync(name);
165 |
166 | if (HasNotifications())
167 | return Result();
168 |
169 | if (!allUsers.HasValue)
170 | return Ok(new ResultViewModel
171 | {
172 | Message = "Nenhum usuário foi encontrado com o nome informado",
173 | Success = true,
174 | Data = null
175 | });
176 |
177 | return Ok(new ResultViewModel
178 | {
179 | Message = "Usuário encontrado com sucesso!",
180 | Success = true,
181 | Data = allUsers
182 | });
183 | }
184 |
185 |
186 | [HttpGet]
187 | [Authorize]
188 | [Route("/api/v1/users/search-by-email")]
189 | public async Task SearchByEmailAsync([FromQuery] string email)
190 | {
191 | var allUsers = await _userService.SearchByEmailAsync(email);
192 |
193 | if (HasNotifications())
194 | return Result();
195 |
196 | if (!allUsers.HasValue)
197 | return Ok(new ResultViewModel
198 | {
199 | Message = "Nenhum usuário foi encontrado com o email informado",
200 | Success = true,
201 | Data = null
202 | });
203 |
204 | return Ok(new ResultViewModel
205 | {
206 | Message = "Usuário encontrado com sucesso!",
207 | Success = true,
208 | Data = allUsers
209 | });
210 | }
211 | }
212 | }
--------------------------------------------------------------------------------
/src/1 - Manager.API/Manager.API.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | c6e1e20b-38d4-4725-b92c-46eff0fb5174
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/1 - Manager.API/Middlewares/ExceptionHandlerMiddleware.cs:
--------------------------------------------------------------------------------
1 | using Manager.API.Utilities;
2 | using Microsoft.AspNetCore.Builder;
3 | using Microsoft.AspNetCore.Diagnostics;
4 | using Microsoft.AspNetCore.Http;
5 |
6 | namespace manager.API.Middlewares
7 | {
8 | public static class ExceptionHandlerMiddleware
9 | {
10 | public static void UseCustomExceptionHandler(this IApplicationBuilder app)
11 | {
12 | app.UseExceptionHandler(appError =>
13 | {
14 | appError.Run(async context =>
15 | {
16 | context.Response.StatusCode = StatusCodes.Status500InternalServerError;
17 | context.Response.ContentType = "application/json";
18 |
19 | var contextFeature = context.Features.Get();
20 |
21 | if (contextFeature != null)
22 | await context.Response.WriteAsJsonAsync(Responses.InternalServerErrorMessage());
23 | });
24 | });
25 | }
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/1 - Manager.API/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Hosting;
2 | using Microsoft.Extensions.Configuration;
3 | using Microsoft.Extensions.Hosting;
4 |
5 | namespace Manager.API
6 | {
7 | public class Program
8 | {
9 | public static void Main(string[] args)
10 | {
11 | CreateHostBuilder(args).Build().Run();
12 | }
13 |
14 | public static IHostBuilder CreateHostBuilder(string[] args) =>
15 | Host.CreateDefaultBuilder(args)
16 | .ConfigureAppConfiguration((context, config) =>
17 | {
18 | if (context.HostingEnvironment.IsProduction())
19 | {
20 | var builtConfig = config.Build();
21 | config.AddAzureKeyVault(
22 | builtConfig["AzureKeyVault:Vault"],
23 | builtConfig["AzureKeyVault:ClientId"],
24 | builtConfig["AzureKeyVault:ClientSecret"]);
25 | }
26 | })
27 | .ConfigureWebHostDefaults(webBuilder =>
28 | {
29 | webBuilder.UseStartup();
30 | });
31 | }
32 | }
--------------------------------------------------------------------------------
/src/1 - Manager.API/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:4279",
8 | "sslPort": 44380
9 | }
10 | },
11 | "profiles": {
12 | "IIS Express": {
13 | "commandName": "IISExpress",
14 | "launchBrowser": true,
15 | "launchUrl": "swagger",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | },
20 | "Manager.API": {
21 | "commandName": "Project",
22 | "dotnetRunMessages": "true",
23 | "launchBrowser": true,
24 | "launchUrl": "swagger",
25 | "applicationUrl": "https://localhost:5001;http://localhost:5000",
26 | "environmentVariables": {
27 | "ASPNETCORE_ENVIRONMENT": "Development"
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/1 - Manager.API/Startup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using AutoMapper;
4 | using EscNet.IoC.Cryptography;
5 | using EscNet.IoC.Hashers;
6 | using Isopoh.Cryptography.Argon2;
7 | using manager.API.Middlewares;
8 | using Manager.API.Token;
9 | using Manager.API.ViewModes;
10 | using Manager.Core.Communication.Handlers;
11 | using Manager.Core.Communication.Mediator;
12 | using Manager.Core.Communication.Mediator.Interfaces;
13 | using Manager.Core.Communication.Messages.Notifications;
14 | using Manager.Domain.Entities;
15 | using Manager.Infra.Context;
16 | using Manager.Infra.Interfaces;
17 | using Manager.Infra.Repositories;
18 | using Manager.Services.DTO;
19 | using Manager.Services.Interfaces;
20 | using Manager.Services.Services;
21 | using MediatR;
22 | using Microsoft.AspNetCore.Authentication.JwtBearer;
23 | using Microsoft.AspNetCore.Builder;
24 | using Microsoft.AspNetCore.Hosting;
25 | using Microsoft.EntityFrameworkCore;
26 | using Microsoft.Extensions.Configuration;
27 | using Microsoft.Extensions.DependencyInjection;
28 | using Microsoft.Extensions.Hosting;
29 | using Microsoft.Extensions.Logging;
30 | using Microsoft.IdentityModel.Tokens;
31 | using Microsoft.OpenApi.Models;
32 |
33 | namespace Manager.API
34 | {
35 | public class Startup
36 | {
37 | public Startup(IConfiguration configuration)
38 | {
39 | Configuration = configuration;
40 | }
41 |
42 | public IConfiguration Configuration { get; }
43 |
44 | // This method gets called by the runtime. Use this method to add services to the container.
45 | public void ConfigureServices(IServiceCollection services)
46 | {
47 | services.AddControllers();
48 | services.AddSingleton(cfg => Configuration);
49 |
50 | #region Jwt
51 |
52 | var secretKey = Configuration["Jwt:Key"];
53 |
54 | services.AddAuthentication(x =>
55 | {
56 | x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
57 | x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
58 | })
59 | .AddJwtBearer(x =>
60 | {
61 | x.RequireHttpsMetadata = false;
62 | x.SaveToken = true;
63 | x.TokenValidationParameters = new TokenValidationParameters
64 | {
65 | ValidateIssuerSigningKey = true,
66 | IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey)),
67 | ValidateIssuer = false,
68 | ValidateAudience = false
69 | };
70 | });
71 |
72 | #endregion
73 |
74 | #region AutoMapper
75 |
76 | var autoMapperConfig = new MapperConfiguration(cfg =>
77 | {
78 | cfg.CreateMap().ReverseMap();
79 | cfg.CreateMap().ReverseMap();
80 | cfg.CreateMap().ReverseMap();
81 | });
82 |
83 | services.AddSingleton(autoMapperConfig.CreateMapper());
84 |
85 | #endregion
86 |
87 | #region Database
88 |
89 | services.AddDbContext(options => options
90 | .UseSqlServer(Configuration["ConnectionStrings:ManagerAPISqlServer"])
91 | .EnableSensitiveDataLogging()
92 | .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())),
93 | ServiceLifetime.Transient);
94 |
95 | #endregion
96 |
97 | #region Repositories
98 |
99 | services.AddScoped();
100 |
101 | #endregion
102 |
103 | #region Services
104 |
105 | services.AddScoped();
106 |
107 | services.AddScoped();
108 |
109 | #endregion
110 |
111 | #region Swagger
112 |
113 | services.AddSwaggerGen(c =>
114 | {
115 | c.SwaggerDoc("v1", new OpenApiInfo
116 | {
117 | Title = "Manager API",
118 | Version = "v1",
119 | Description = "API construída na serie de vídeos no canal Lucas Eschechola.",
120 | Contact = new OpenApiContact
121 | {
122 | Name = "Lucas Eschechola",
123 | Email = "lucas.gabriel@eu.com",
124 | Url = new Uri("https://eschechola.com.br")
125 | },
126 | });
127 |
128 | c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
129 | {
130 | In = ParameterLocation.Header,
131 | Description = "Por favor utilize Bearer ",
132 | Name = "Authorization",
133 | Type = SecuritySchemeType.ApiKey
134 | });
135 | c.AddSecurityRequirement(new OpenApiSecurityRequirement {
136 | {
137 | new OpenApiSecurityScheme
138 | {
139 | Reference = new OpenApiReference
140 | {
141 | Type = ReferenceType.SecurityScheme,
142 | Id = "Bearer"
143 | }
144 | },
145 | new string[] { }
146 | }
147 | });
148 | });
149 |
150 | #endregion
151 |
152 | #region Hash
153 |
154 | var config = new Argon2Config
155 | {
156 | Type = Argon2Type.DataIndependentAddressing,
157 | Version = Argon2Version.Nineteen,
158 | TimeCost = int.Parse(Configuration["Hash:TimeCost"]),
159 | MemoryCost = int.Parse(Configuration["Hash:MemoryCost"]),
160 | Lanes = int.Parse(Configuration["Hash:Lanes"]),
161 | Threads = Environment.ProcessorCount,
162 | Salt = Encoding.UTF8.GetBytes(Configuration["Hash:Salt"]),
163 | HashLength = int.Parse(Configuration["Hash:HashLength"])
164 | };
165 |
166 | services.AddArgon2IdHasher(config);
167 |
168 | #endregion
169 |
170 | #region Mediator
171 |
172 | services.AddMediatR(typeof(Startup));
173 | services.AddScoped, DomainNotificationHandler>();
174 | services.AddScoped();
175 |
176 | #endregion
177 | }
178 |
179 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
180 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
181 | {
182 | if(env.IsDevelopment())
183 | app.UseDeveloperExceptionPage();
184 |
185 | app.UseSwagger();
186 | app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Manager.API v1"));
187 |
188 | app.UseHttpsRedirection();
189 |
190 | app.UseRouting();
191 |
192 | app.UseAuthentication();
193 |
194 | app.UseAuthorization();
195 |
196 | app.UseCustomExceptionHandler();
197 |
198 | app.UseEndpoints(endpoints =>
199 | {
200 | endpoints.MapControllers();
201 | });
202 | }
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/src/1 - Manager.API/Token/ITokenService.cs:
--------------------------------------------------------------------------------
1 | namespace Manager.API.Token{
2 | public interface ITokenService{
3 | string GenerateToken();
4 | }
5 | }
--------------------------------------------------------------------------------
/src/1 - Manager.API/Token/TokenService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IdentityModel.Tokens.Jwt;
3 | using System.Security.Claims;
4 | using System.Text;
5 | using Microsoft.Extensions.Configuration;
6 | using Microsoft.IdentityModel.Tokens;
7 |
8 | namespace Manager.API.Token{
9 | public class TokenService : ITokenService
10 | {
11 | private readonly IConfiguration _configuration;
12 |
13 | public TokenService(IConfiguration configuration)
14 | {
15 | _configuration = configuration;
16 | }
17 |
18 | public string GenerateToken()
19 | {
20 | var tokenHandler = new JwtSecurityTokenHandler();
21 |
22 | var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]);
23 |
24 | var tokenDescriptor = new SecurityTokenDescriptor
25 | {
26 | Subject = new ClaimsIdentity(new Claim[]
27 | {
28 | new Claim(ClaimTypes.Name, _configuration["Jwt:Login"]),
29 | new Claim(ClaimTypes.Role, "User")
30 | }),
31 | Expires = DateTime.UtcNow.AddHours(int.Parse(_configuration["Jwt:HoursToExpire"])),
32 |
33 | SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
34 | };
35 |
36 | var token = tokenHandler.CreateToken(tokenDescriptor);
37 |
38 | return tokenHandler.WriteToken(token);
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/src/1 - Manager.API/Utilities/Responses.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Manager.API.ViewModes;
3 |
4 | namespace Manager.API.Utilities{
5 | public static class Responses{
6 | public static ResultViewModel ApplicationErrorMessage()
7 | {
8 | return new ResultViewModel
9 | {
10 | Message = "Ocorreu algum erro interno na aplicação, por favor tente novamente.",
11 | Success = false,
12 | Data = null
13 | };
14 | }
15 |
16 | public static ResultViewModel DomainErrorMessage(string message)
17 | {
18 | return new ResultViewModel
19 | {
20 | Message = message,
21 | Success = false,
22 | Data = null
23 | };
24 | }
25 |
26 | public static ResultViewModel DomainErrorMessage(string message, IReadOnlyCollection errors)
27 | {
28 | return new ResultViewModel
29 | {
30 | Message = message,
31 | Success = false,
32 | Data = errors
33 | };
34 | }
35 |
36 | public static ResultViewModel UnauthorizedErrorMessage()
37 | {
38 | return new ResultViewModel
39 | {
40 | Message = "A combinação de login e senha está incorreta!",
41 | Success = false,
42 | Data = null
43 | };
44 | }
45 |
46 | public static ResultViewModel InternalServerErrorMessage()
47 | {
48 | return new ResultViewModel
49 | {
50 | Message = "Ocorreu um erro interno na aplicação, por favor tente novamente.",
51 | Success = false,
52 | Data = null
53 | };
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/src/1 - Manager.API/ViewModels/CreateUserViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace Manager.API.ViewModes{
4 | public class CreateUserViewModel{
5 |
6 | [Required(ErrorMessage = "O nome não pode ser vazio.")]
7 | [MinLength(3, ErrorMessage = "O nome deve ter no mínimo 3 caracteres.")]
8 | [MaxLength(80, ErrorMessage = "O nome deve ter no máximo 80 caracteres.")]
9 | public string Name { get; set; }
10 |
11 | [Required(ErrorMessage = "O email não pode ser vazio.")]
12 | [MinLength(10, ErrorMessage = "O email deve ter no mínimo 10 caracteres.")]
13 | [MaxLength(180, ErrorMessage = "O email deve ter no máximo 180 caracteres.")]
14 | [RegularExpression(@"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$",
15 | ErrorMessage = "O email informado não é válido.")]
16 | public string Email { get; set; }
17 |
18 | [Required(ErrorMessage = "A senha não pode ser vazia.")]
19 | [MinLength(10, ErrorMessage = "A senha deve ter no mínimo 10 caracteres.")]
20 | [MaxLength(80, ErrorMessage = "A senha deve ter no máximo 80 caracteres.")]
21 | public string Password { get; set; }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/1 - Manager.API/ViewModels/LoginViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace Manager.API.ViewModes{
4 | public class LoginViewModel
5 | {
6 | [Required(ErrorMessage = "O login não pode vazio.")]
7 | public string Login { get; set; }
8 |
9 | [Required(ErrorMessage = "A senha não pode vazio.")]
10 | public string Password { get; set; }
11 | }
12 | }
--------------------------------------------------------------------------------
/src/1 - Manager.API/ViewModels/ResultViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace Manager.API.ViewModes{
2 | public class ResultViewModel{
3 | public string Message { get; set; }
4 | public bool Success { get; set; }
5 | public dynamic Data { get; set; }
6 | }
7 | }
--------------------------------------------------------------------------------
/src/1 - Manager.API/ViewModels/UpdateUserViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace Manager.API.ViewModes{
4 | public class UpdateUserViewModel{
5 | [Required(ErrorMessage = "O Id não pode ser vazio.")]
6 | [Range(1, int.MaxValue, ErrorMessage = "O id não pode ser menor que 1.")]
7 | public int Id { get; set; }
8 |
9 | [Required(ErrorMessage = "O nome não pode ser vazio.")]
10 | [MinLength(3, ErrorMessage = "O nome deve ter no mínimo 3 caracteres.")]
11 | [MaxLength(80, ErrorMessage = "O nome deve ter no máximo 80 caracteres.")]
12 | public string Name { get; set; }
13 |
14 | [Required(ErrorMessage = "O email não pode ser vazio.")]
15 | [MinLength(10, ErrorMessage = "O email deve ter no mínimo 10 caracteres.")]
16 | [MaxLength(180, ErrorMessage = "O email deve ter no máximo 180 caracteres.")]
17 | [RegularExpression(@"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$",
18 | ErrorMessage = "O email informado não é válido.")]
19 | public string Email { get; set; }
20 |
21 | [Required(ErrorMessage = "A senha não pode ser vazia.")]
22 | [MinLength(6, ErrorMessage = "A senha deve ter no mínimo 6 caracteres.")]
23 | [MaxLength(80, ErrorMessage = "A senha deve ter no máximo 80 caracteres.")]
24 | public string Password { get; set; }
25 | }
26 | }
--------------------------------------------------------------------------------
/src/1 - Manager.API/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/1 - Manager.API/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 |
10 | "Hash": {
11 | "TimeCost": 10,
12 | "Lanes": 5,
13 | "MemoryCost": 32768,
14 | "HashLength": 20,
15 | "Salt": ""
16 | },
17 |
18 | "ConnectionStrings": {
19 | "ManagerAPISqlServer": ""
20 | },
21 |
22 | "Jwt": {
23 | "Key": "",
24 | "Login": "",
25 | "Password": "",
26 | "HoursToExpire": "1"
27 | },
28 |
29 | "AllowedHosts": "*"
30 | }
--------------------------------------------------------------------------------
/src/2 - Manager.Domain/Entities/Base.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using FluentValidation.Results;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace Manager.Domain.Entities{
7 | public abstract class Base{
8 | public long Id { get; set; }
9 |
10 | internal List _errors;
11 | public IReadOnlyCollection Errors => _errors;
12 |
13 | public bool IsValid
14 | => _errors.Count == 0;
15 |
16 | private void AddErrorList(IList errors)
17 | {
18 | foreach (var error in errors)
19 | _errors.Add(error.ErrorMessage);
20 | }
21 |
22 | private void CleanErrors()
23 | => _errors.Clear();
24 |
25 | protected bool Validate(T validator, J obj)
26 | where T : AbstractValidator
27 | {
28 | var validation = validator.Validate(obj);
29 |
30 | if (validation.Errors.Count > 0)
31 | AddErrorList(validation.Errors);
32 |
33 | return _errors.Count == 0;
34 | }
35 |
36 | public string ErrorsToString()
37 | {
38 | var builder = new StringBuilder();
39 |
40 | foreach (var error in _errors)
41 | builder.Append(" " + error);
42 |
43 | return builder.ToString();
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/src/2 - Manager.Domain/Entities/User.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Manager.Domain.Validators;
3 |
4 | namespace Manager.Domain.Entities
5 | {
6 | public class User : Base {
7 |
8 | //Propriedades
9 | public string Name { get; private set; }
10 | public string Email { get; private set; }
11 | public string Password { get; private set; }
12 |
13 | //EF
14 | protected User(){}
15 |
16 | public User(string name, string email, string password)
17 | {
18 | Name = name;
19 | Email = email;
20 | Password = password;
21 | _errors = new List();
22 |
23 | Validate();
24 | }
25 |
26 |
27 | //Comportamentos
28 | public void SetName(string name){
29 | Name = name;
30 | Validate();
31 | }
32 |
33 | public void SetPassword(string password){
34 | Password = password;
35 | Validate();
36 | }
37 |
38 | public void SetEmail(string email){
39 | Email = email;
40 | Validate();
41 | }
42 |
43 | //Autovalida
44 | public bool Validate()
45 | => base.Validate(new UserValidator(), this);
46 | }
47 | }
--------------------------------------------------------------------------------
/src/2 - Manager.Domain/Manager.Domain.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/2 - Manager.Domain/Validators/UserValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using Manager.Domain.Entities;
3 |
4 | namespace Manager.Domain.Validators{
5 | public class UserValidator : AbstractValidator{
6 | public UserValidator()
7 | {
8 | RuleFor(x => x)
9 | .NotEmpty()
10 | .WithMessage("A entidade não pode ser vazia.")
11 |
12 | .NotNull()
13 | .WithMessage("A entidade não pode ser nula.");
14 |
15 | RuleFor(x => x.Name)
16 | .NotNull()
17 | .WithMessage("O nome não pode ser nulo.")
18 |
19 | .NotEmpty()
20 | .WithMessage("O nome não pode ser vazio.")
21 |
22 | .MinimumLength(3)
23 | .WithMessage("O nome deve ter no mínimo 3 caracteres.")
24 |
25 | .MaximumLength(80)
26 | .WithMessage("O nome deve ter no máximo 80 caracteres.");
27 |
28 | RuleFor(x => x.Password)
29 | .NotNull()
30 | .WithMessage("A senha não pode ser nula.")
31 |
32 | .NotEmpty()
33 | .WithMessage("A senha não pode ser vazia.")
34 |
35 | .MinimumLength(6)
36 | .WithMessage("A senha deve ter no mínimo 6 caracteres.")
37 |
38 | .MaximumLength(80)
39 | .WithMessage("A senha deve ter no máximo 30 caracteres.");
40 |
41 | RuleFor(x=>x.Email)
42 | .NotNull()
43 | .WithMessage("O email não pode ser nulo.")
44 |
45 | .NotEmpty()
46 | .WithMessage("O email não pode ser vazio.")
47 |
48 | .MinimumLength(10)
49 | .WithMessage("O email deve ter no mínimo 10 caracteres.")
50 |
51 | .MaximumLength(180)
52 | .WithMessage("O email deve ter no máximo 180 caracteres.")
53 |
54 | .Matches(@"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$")
55 | .WithMessage("O email informado não é válido.");
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/src/3 - Manager.Services/DTO/UserDTO.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace Manager.Services.DTO{
4 | public class UserDTO{
5 | public long Id { get; set; }
6 | public string Name { get; set; }
7 | public string Email { get; set; }
8 |
9 | [JsonIgnore]
10 | public string Password { get; set; }
11 |
12 | public UserDTO()
13 | {}
14 |
15 | public UserDTO(long id, string name, string email, string password)
16 | {
17 | Id = id;
18 | Name = name;
19 | Email = email;
20 | Password = password;
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/3 - Manager.Services/Interfaces/IUserService.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using System.Collections.Generic;
3 | using Manager.Services.DTO;
4 | using Manager.Core.Structs;
5 |
6 | namespace Manager.Services.Interfaces{
7 | public interface IUserService{
8 | Task> CreateAsync(UserDTO userDTO);
9 | Task> UpdateAsync(UserDTO userDTO);
10 | Task RemoveAsync(long id);
11 | Task> GetAsync(long id);
12 | Task>> GetAllAsync();
13 | Task>> SearchByNameAsync(string name);
14 | Task>> SearchByEmailAsync(string email);
15 | Task> GetByEmailAsync(string email);
16 | }
17 | }
--------------------------------------------------------------------------------
/src/3 - Manager.Services/Manager.Services.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/3 - Manager.Services/Services/UserService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq.Expressions;
4 | using System.Threading.Tasks;
5 | using AutoMapper;
6 | using EscNet.Cryptography.Interfaces;
7 | using EscNet.Hashers.Interfaces.Algorithms;
8 | using Manager.Core.Communication.Mediator.Interfaces;
9 | using Manager.Core.Communication.Messages.Notifications;
10 | using Manager.Core.Enum;
11 | using Manager.Core.Structs;
12 | using Manager.Core.Validations.Message;
13 | using Manager.Domain.Entities;
14 | using Manager.Infra.Interfaces;
15 | using Manager.Services.DTO;
16 | using Manager.Services.Interfaces;
17 |
18 | namespace Manager.Services.Services
19 | {
20 | public class UserService : IUserService{
21 | private readonly IMapper _mapper;
22 | private readonly IUserRepository _userRepository;
23 | private readonly IArgon2IdHasher _argon2IdHasher;
24 | private readonly IMediatorHandler _mediator;
25 |
26 | public UserService(
27 | IMapper mapper,
28 | IUserRepository userRepository,
29 | IArgon2IdHasher argon2IdHasher,
30 | IMediatorHandler mediator)
31 | {
32 | _mapper = mapper;
33 | _userRepository = userRepository;
34 | _argon2IdHasher = argon2IdHasher;
35 | _mediator = mediator;
36 | }
37 |
38 | public async Task> CreateAsync(UserDTO userDTO)
39 | {
40 | Expression> filter = user
41 | => user.Email.ToLower() == userDTO.Email.ToLower();
42 |
43 | var userExists = await _userRepository.GetAsync(filter);
44 |
45 | if (userExists != null)
46 | {
47 | await _mediator.PublishDomainNotificationAsync(new DomainNotification(
48 | ErrorMessages.UserAlreadyExists,
49 | DomainNotificationType.UserAlreadyExists));
50 |
51 | return new Optional();
52 | }
53 |
54 | var user = _mapper.Map(userDTO);
55 | user.Validate();
56 |
57 | if (!user.IsValid)
58 | {
59 | await _mediator.PublishDomainNotificationAsync(new DomainNotification(
60 | ErrorMessages.UserInvalid(user.ErrorsToString()),
61 | DomainNotificationType.UserInvalid));
62 |
63 | return new Optional();
64 | }
65 |
66 | user.SetPassword(_argon2IdHasher.Hash(user.Password));
67 |
68 | var userCreated = await _userRepository.CreateAsync(user);
69 |
70 | return _mapper.Map(userCreated);
71 | }
72 |
73 | public async Task> UpdateAsync(UserDTO userDTO){
74 | var userExists = await _userRepository.GetAsync(userDTO.Id);
75 |
76 | if (userExists == null)
77 | {
78 | await _mediator.PublishDomainNotificationAsync(new DomainNotification(
79 | ErrorMessages.UserNotFound,
80 | DomainNotificationType.UserNotFound));
81 |
82 | return new Optional();
83 | }
84 |
85 | var user = _mapper.Map(userDTO);
86 | user.Validate();
87 |
88 | if (!user.IsValid)
89 | {
90 | await _mediator.PublishDomainNotificationAsync(new DomainNotification(
91 | ErrorMessages.UserInvalid(user.ErrorsToString()),
92 | DomainNotificationType.UserInvalid));
93 |
94 | return new Optional();
95 | }
96 |
97 | var sendedHashedPassword = _argon2IdHasher.Hash(user.Password);
98 |
99 | if(sendedHashedPassword != userExists.Password)
100 | user.SetPassword(sendedHashedPassword);
101 |
102 | var userUpdated = await _userRepository.UpdateAsync(user);
103 |
104 | return _mapper.Map(userUpdated);
105 | }
106 | public async Task RemoveAsync(long id)
107 | => await _userRepository.RemoveAsync(id);
108 |
109 | public async Task> GetAsync(long id){
110 | var user = await _userRepository.GetAsync(id);
111 |
112 | return _mapper.Map(user);
113 | }
114 |
115 | public async Task>> GetAllAsync(){
116 | var allUsers = await _userRepository.GetAllAsync();
117 | var allUsersDTO = _mapper.Map>(allUsers);
118 |
119 | return new Optional>(allUsersDTO);
120 | }
121 |
122 | public async Task>> SearchByNameAsync(string name){
123 | Expression> filter = u
124 | => u.Name.ToLower().Contains(name.ToLower());
125 |
126 | var allUsers = await _userRepository.SearchAsync(filter);
127 | var allUsersDTO = _mapper.Map>(allUsers);
128 |
129 | return new Optional>(allUsersDTO);
130 | }
131 |
132 | public async Task>> SearchByEmailAsync(string email){
133 | Expression> filter = user
134 | => user.Email.ToLower().Contains(email.ToLower());
135 |
136 | var allUsers = await _userRepository.SearchAsync(filter);
137 | var allUsersDTO = _mapper.Map>(allUsers);
138 |
139 | return new Optional>(allUsersDTO);
140 | }
141 |
142 | public async Task> GetByEmailAsync(string email){
143 | Expression> filter = user
144 | => user.Email.ToLower() == email.ToLower();
145 |
146 | var user = await _userRepository.GetAsync(filter);
147 | var userDTO = _mapper.Map(user);
148 |
149 | return new Optional(userDTO);
150 | }
151 | }
152 | }
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Context/ManagerContext.cs:
--------------------------------------------------------------------------------
1 | using Manager.Domain.Entities;
2 | using Manager.Infra.Mappings;
3 | using Microsoft.EntityFrameworkCore;
4 |
5 | namespace Manager.Infra.Context{
6 | public class ManagerContext : DbContext{
7 | public ManagerContext()
8 | {}
9 |
10 | public ManagerContext(DbContextOptions options) : base(options)
11 | {}
12 |
13 | public virtual DbSet Users { get; set; }
14 |
15 | protected override void OnModelCreating(ModelBuilder builder)
16 | {
17 | builder.ApplyConfiguration(new UserMap());
18 | }
19 |
20 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
21 | => optionsBuilder.UseSqlServer();
22 | }
23 | }
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Interfaces/IBaseRepository.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Manager.Domain.Entities;
3 | using System.Collections.Generic;
4 | using System.Linq.Expressions;
5 | using System;
6 |
7 | namespace Manager.Infra.Interfaces{
8 | public interface IBaseRepository where T : Base{
9 | Task CreateAsync(T obj);
10 | Task UpdateAsync(T obj);
11 | Task RemoveAsync(long id);
12 | Task> GetAllAsync();
13 | Task GetAsync(long id);
14 | Task GetAsync(Expression> expression, bool asNoTracking = true);
15 | Task> SearchAsync(Expression> expression, bool asNoTracking = true);
16 | }
17 | }
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Interfaces/IUserRepository.cs:
--------------------------------------------------------------------------------
1 | using Manager.Domain.Entities;
2 |
3 | namespace Manager.Infra.Interfaces
4 | {
5 | public interface IUserRepository : IBaseRepository
6 | {
7 | }
8 | }
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Manager.Infra.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 | runtime; build; native; contentfiles; analyzers; buildtransitive
11 | all
12 |
13 |
14 |
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 | all
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Mappings/UserMap.cs:
--------------------------------------------------------------------------------
1 | using Manager.Domain.Entities;
2 | using Microsoft.EntityFrameworkCore;
3 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
4 |
5 | namespace Manager.Infra.Mappings{
6 | public class UserMap : IEntityTypeConfiguration
7 | {
8 | public void Configure(EntityTypeBuilder builder)
9 | {
10 | builder.ToTable("User");
11 |
12 | builder.HasKey(x => x.Id);
13 |
14 | builder.Property(x=>x.Id)
15 | .UseIdentityColumn()
16 | .HasColumnType("BIGINT");
17 |
18 | builder.Property(x => x.Name)
19 | .IsRequired()
20 | .HasMaxLength(80)
21 | .HasColumnName("name")
22 | .HasColumnType("VARCHAR(80)");
23 |
24 | builder.Property(x => x.Password)
25 | .IsRequired()
26 | .HasMaxLength(1000)
27 | .HasColumnName("password")
28 | .HasColumnType("VARCHAR(1000)");
29 |
30 | builder.Property(x => x.Email)
31 | .IsRequired()
32 | .HasMaxLength(180)
33 | .HasColumnName("email")
34 | .HasColumnType("VARCHAR(180)");
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Migrations/20201231061725_InitialMigration.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using Manager.Infra.Context;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Metadata;
6 | using Microsoft.EntityFrameworkCore.Migrations;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 |
9 | namespace Manager.Infra.Migrations
10 | {
11 | [DbContext(typeof(ManagerContext))]
12 | [Migration("20201231061725_InitialMigration")]
13 | partial class InitialMigration
14 | {
15 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
16 | {
17 | #pragma warning disable 612, 618
18 | modelBuilder
19 | .UseIdentityColumns()
20 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
21 | .HasAnnotation("ProductVersion", "5.0.1");
22 |
23 | modelBuilder.Entity("Manager.Domain.Entities.User", b =>
24 | {
25 | b.Property("Id")
26 | .ValueGeneratedOnAdd()
27 | .HasColumnType("BIGINT")
28 | .UseIdentityColumn();
29 |
30 | b.Property("Email")
31 | .IsRequired()
32 | .HasMaxLength(180)
33 | .HasColumnType("VARCHAR(180)")
34 | .HasColumnName("email");
35 |
36 | b.Property("Name")
37 | .IsRequired()
38 | .HasMaxLength(80)
39 | .HasColumnType("VARCHAR(80)")
40 | .HasColumnName("name");
41 |
42 | b.Property("Password")
43 | .IsRequired()
44 | .HasMaxLength(30)
45 | .HasColumnType("VARCHAR(30)")
46 | .HasColumnName("password");
47 |
48 | b.HasKey("Id");
49 |
50 | b.ToTable("User");
51 | });
52 | #pragma warning restore 612, 618
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Migrations/20201231061725_InitialMigration.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore.Migrations;
2 |
3 | namespace Manager.Infra.Migrations
4 | {
5 | public partial class InitialMigration : Migration
6 | {
7 | protected override void Up(MigrationBuilder migrationBuilder)
8 | {
9 | migrationBuilder.CreateTable(
10 | name: "User",
11 | columns: table => new
12 | {
13 | Id = table.Column(type: "BIGINT", nullable: false)
14 | .Annotation("SqlServer:Identity", "1, 1"),
15 | name = table.Column(type: "VARCHAR(80)", maxLength: 80, nullable: false),
16 | email = table.Column(type: "VARCHAR(180)", maxLength: 180, nullable: false),
17 | password = table.Column(type: "VARCHAR(30)", maxLength: 30, nullable: false)
18 | },
19 | constraints: table =>
20 | {
21 | table.PrimaryKey("PK_User", x => x.Id);
22 | });
23 | }
24 |
25 | protected override void Down(MigrationBuilder migrationBuilder)
26 | {
27 | migrationBuilder.DropTable(
28 | name: "User");
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Migrations/20201231062142_UpdateNameLength.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using Manager.Infra.Context;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Metadata;
6 | using Microsoft.EntityFrameworkCore.Migrations;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 |
9 | namespace Manager.Infra.Migrations
10 | {
11 | [DbContext(typeof(ManagerContext))]
12 | [Migration("20201231062142_UpdateNameLength")]
13 | partial class UpdateNameLength
14 | {
15 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
16 | {
17 | #pragma warning disable 612, 618
18 | modelBuilder
19 | .UseIdentityColumns()
20 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
21 | .HasAnnotation("ProductVersion", "5.0.1");
22 |
23 | modelBuilder.Entity("Manager.Domain.Entities.User", b =>
24 | {
25 | b.Property("Id")
26 | .ValueGeneratedOnAdd()
27 | .HasColumnType("BIGINT")
28 | .UseIdentityColumn();
29 |
30 | b.Property("Email")
31 | .IsRequired()
32 | .HasMaxLength(180)
33 | .HasColumnType("VARCHAR(180)")
34 | .HasColumnName("email");
35 |
36 | b.Property("Name")
37 | .IsRequired()
38 | .HasMaxLength(80)
39 | .HasColumnType("VARCHAR(80)")
40 | .HasColumnName("name");
41 |
42 | b.Property("Password")
43 | .IsRequired()
44 | .HasMaxLength(30)
45 | .HasColumnType("VARCHAR(30)")
46 | .HasColumnName("password");
47 |
48 | b.HasKey("Id");
49 |
50 | b.ToTable("User");
51 | });
52 | #pragma warning restore 612, 618
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Migrations/20201231062142_UpdateNameLength.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore.Migrations;
2 |
3 | namespace Manager.Infra.Migrations
4 | {
5 | public partial class UpdateNameLength : Migration
6 | {
7 | protected override void Up(MigrationBuilder migrationBuilder)
8 | {
9 |
10 | }
11 |
12 | protected override void Down(MigrationBuilder migrationBuilder)
13 | {
14 |
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Migrations/20201231062353_UpdateNameLength2.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using Manager.Infra.Context;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Metadata;
6 | using Microsoft.EntityFrameworkCore.Migrations;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 |
9 | namespace Manager.Infra.Migrations
10 | {
11 | [DbContext(typeof(ManagerContext))]
12 | [Migration("20201231062353_UpdateNameLength2")]
13 | partial class UpdateNameLength2
14 | {
15 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
16 | {
17 | #pragma warning disable 612, 618
18 | modelBuilder
19 | .UseIdentityColumns()
20 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
21 | .HasAnnotation("ProductVersion", "5.0.1");
22 |
23 | modelBuilder.Entity("Manager.Domain.Entities.User", b =>
24 | {
25 | b.Property("Id")
26 | .ValueGeneratedOnAdd()
27 | .HasColumnType("BIGINT")
28 | .UseIdentityColumn();
29 |
30 | b.Property("Email")
31 | .IsRequired()
32 | .HasMaxLength(180)
33 | .HasColumnType("VARCHAR(180)")
34 | .HasColumnName("email");
35 |
36 | b.Property("Name")
37 | .IsRequired()
38 | .HasMaxLength(50)
39 | .HasColumnType("VARCHAR(50)")
40 | .HasColumnName("name");
41 |
42 | b.Property("Password")
43 | .IsRequired()
44 | .HasMaxLength(30)
45 | .HasColumnType("VARCHAR(30)")
46 | .HasColumnName("password");
47 |
48 | b.HasKey("Id");
49 |
50 | b.ToTable("User");
51 | });
52 | #pragma warning restore 612, 618
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Migrations/20201231062353_UpdateNameLength2.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore.Migrations;
2 |
3 | namespace Manager.Infra.Migrations
4 | {
5 | public partial class UpdateNameLength2 : Migration
6 | {
7 | protected override void Up(MigrationBuilder migrationBuilder)
8 | {
9 | migrationBuilder.AlterColumn(
10 | name: "name",
11 | table: "User",
12 | type: "VARCHAR(50)",
13 | maxLength: 50,
14 | nullable: false,
15 | oldClrType: typeof(string),
16 | oldType: "VARCHAR(80)",
17 | oldMaxLength: 80);
18 | }
19 |
20 | protected override void Down(MigrationBuilder migrationBuilder)
21 | {
22 | migrationBuilder.AlterColumn(
23 | name: "name",
24 | table: "User",
25 | type: "VARCHAR(80)",
26 | maxLength: 80,
27 | nullable: false,
28 | oldClrType: typeof(string),
29 | oldType: "VARCHAR(50)",
30 | oldMaxLength: 50);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Migrations/20201231062529_UpdateNameLengthToLessCharacters.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using Manager.Infra.Context;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Metadata;
6 | using Microsoft.EntityFrameworkCore.Migrations;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 |
9 | namespace Manager.Infra.Migrations
10 | {
11 | [DbContext(typeof(ManagerContext))]
12 | [Migration("20201231062529_UpdateNameLengthToLessCharacters")]
13 | partial class UpdateNameLengthToLessCharacters
14 | {
15 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
16 | {
17 | #pragma warning disable 612, 618
18 | modelBuilder
19 | .UseIdentityColumns()
20 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
21 | .HasAnnotation("ProductVersion", "5.0.1");
22 |
23 | modelBuilder.Entity("Manager.Domain.Entities.User", b =>
24 | {
25 | b.Property("Id")
26 | .ValueGeneratedOnAdd()
27 | .HasColumnType("BIGINT")
28 | .UseIdentityColumn();
29 |
30 | b.Property("Email")
31 | .IsRequired()
32 | .HasMaxLength(180)
33 | .HasColumnType("VARCHAR(180)")
34 | .HasColumnName("email");
35 |
36 | b.Property("Name")
37 | .IsRequired()
38 | .HasMaxLength(50)
39 | .HasColumnType("VARCHAR(50)")
40 | .HasColumnName("name");
41 |
42 | b.Property("Password")
43 | .IsRequired()
44 | .HasMaxLength(30)
45 | .HasColumnType("VARCHAR(30)")
46 | .HasColumnName("password");
47 |
48 | b.HasKey("Id");
49 |
50 | b.ToTable("User");
51 | });
52 | #pragma warning restore 612, 618
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Migrations/20201231062529_UpdateNameLengthToLessCharacters.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore.Migrations;
2 |
3 | namespace Manager.Infra.Migrations
4 | {
5 | public partial class UpdateNameLengthToLessCharacters : Migration
6 | {
7 | protected override void Up(MigrationBuilder migrationBuilder)
8 | {
9 |
10 | }
11 |
12 | protected override void Down(MigrationBuilder migrationBuilder)
13 | {
14 |
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Migrations/20201231062551_UpdateNameLengthToLessCharacters2.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using Manager.Infra.Context;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Metadata;
6 | using Microsoft.EntityFrameworkCore.Migrations;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 |
9 | namespace Manager.Infra.Migrations
10 | {
11 | [DbContext(typeof(ManagerContext))]
12 | [Migration("20201231062551_UpdateNameLengthToLessCharacters2")]
13 | partial class UpdateNameLengthToLessCharacters2
14 | {
15 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
16 | {
17 | #pragma warning disable 612, 618
18 | modelBuilder
19 | .UseIdentityColumns()
20 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
21 | .HasAnnotation("ProductVersion", "5.0.1");
22 |
23 | modelBuilder.Entity("Manager.Domain.Entities.User", b =>
24 | {
25 | b.Property("Id")
26 | .ValueGeneratedOnAdd()
27 | .HasColumnType("BIGINT")
28 | .UseIdentityColumn();
29 |
30 | b.Property("Email")
31 | .IsRequired()
32 | .HasMaxLength(180)
33 | .HasColumnType("VARCHAR(180)")
34 | .HasColumnName("email");
35 |
36 | b.Property("Name")
37 | .IsRequired()
38 | .HasMaxLength(80)
39 | .HasColumnType("VARCHAR(80)")
40 | .HasColumnName("name");
41 |
42 | b.Property("Password")
43 | .IsRequired()
44 | .HasMaxLength(30)
45 | .HasColumnType("VARCHAR(30)")
46 | .HasColumnName("password");
47 |
48 | b.HasKey("Id");
49 |
50 | b.ToTable("User");
51 | });
52 | #pragma warning restore 612, 618
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Migrations/20201231062551_UpdateNameLengthToLessCharacters2.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore.Migrations;
2 |
3 | namespace Manager.Infra.Migrations
4 | {
5 | public partial class UpdateNameLengthToLessCharacters2 : Migration
6 | {
7 | protected override void Up(MigrationBuilder migrationBuilder)
8 | {
9 | migrationBuilder.AlterColumn(
10 | name: "name",
11 | table: "User",
12 | type: "VARCHAR(80)",
13 | maxLength: 80,
14 | nullable: false,
15 | oldClrType: typeof(string),
16 | oldType: "VARCHAR(50)",
17 | oldMaxLength: 50);
18 | }
19 |
20 | protected override void Down(MigrationBuilder migrationBuilder)
21 | {
22 | migrationBuilder.AlterColumn(
23 | name: "name",
24 | table: "User",
25 | type: "VARCHAR(50)",
26 | maxLength: 50,
27 | nullable: false,
28 | oldClrType: typeof(string),
29 | oldType: "VARCHAR(80)",
30 | oldMaxLength: 80);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Migrations/20210301044804_UpdatePasswordLenght.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using Manager.Infra.Context;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Metadata;
6 | using Microsoft.EntityFrameworkCore.Migrations;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 |
9 | namespace Manager.Infra.Migrations
10 | {
11 | [DbContext(typeof(ManagerContext))]
12 | [Migration("20210301044804_UpdatePasswordLenght")]
13 | partial class UpdatePasswordLenght
14 | {
15 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
16 | {
17 | #pragma warning disable 612, 618
18 | modelBuilder
19 | .UseIdentityColumns()
20 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
21 | .HasAnnotation("ProductVersion", "5.0.1");
22 |
23 | modelBuilder.Entity("Manager.Domain.Entities.User", b =>
24 | {
25 | b.Property("Id")
26 | .ValueGeneratedOnAdd()
27 | .HasColumnType("BIGINT")
28 | .UseIdentityColumn();
29 |
30 | b.Property("Email")
31 | .IsRequired()
32 | .HasMaxLength(180)
33 | .HasColumnType("VARCHAR(180)")
34 | .HasColumnName("email");
35 |
36 | b.Property("Name")
37 | .IsRequired()
38 | .HasMaxLength(80)
39 | .HasColumnType("VARCHAR(80)")
40 | .HasColumnName("name");
41 |
42 | b.Property("Password")
43 | .IsRequired()
44 | .HasMaxLength(1000)
45 | .HasColumnType("VARCHAR(1000)")
46 | .HasColumnName("password");
47 |
48 | b.HasKey("Id");
49 |
50 | b.ToTable("User");
51 | });
52 | #pragma warning restore 612, 618
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Migrations/20210301044804_UpdatePasswordLenght.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore.Migrations;
2 |
3 | namespace Manager.Infra.Migrations
4 | {
5 | public partial class UpdatePasswordLenght : Migration
6 | {
7 | protected override void Up(MigrationBuilder migrationBuilder)
8 | {
9 | migrationBuilder.AlterColumn(
10 | name: "password",
11 | table: "User",
12 | type: "VARCHAR(1000)",
13 | maxLength: 1000,
14 | nullable: false,
15 | oldClrType: typeof(string),
16 | oldType: "VARCHAR(30)",
17 | oldMaxLength: 30);
18 | }
19 |
20 | protected override void Down(MigrationBuilder migrationBuilder)
21 | {
22 | migrationBuilder.AlterColumn(
23 | name: "password",
24 | table: "User",
25 | type: "VARCHAR(30)",
26 | maxLength: 30,
27 | nullable: false,
28 | oldClrType: typeof(string),
29 | oldType: "VARCHAR(1000)",
30 | oldMaxLength: 1000);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Migrations/ManagerContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | //
2 | using Manager.Infra.Context;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Metadata;
6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
7 |
8 | namespace Manager.Infra.Migrations
9 | {
10 | [DbContext(typeof(ManagerContext))]
11 | partial class ManagerContextModelSnapshot : ModelSnapshot
12 | {
13 | protected override void BuildModel(ModelBuilder modelBuilder)
14 | {
15 | #pragma warning disable 612, 618
16 | modelBuilder
17 | .UseIdentityColumns()
18 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
19 | .HasAnnotation("ProductVersion", "5.0.1");
20 |
21 | modelBuilder.Entity("Manager.Domain.Entities.User", b =>
22 | {
23 | b.Property("Id")
24 | .ValueGeneratedOnAdd()
25 | .HasColumnType("BIGINT")
26 | .UseIdentityColumn();
27 |
28 | b.Property("Email")
29 | .IsRequired()
30 | .HasMaxLength(180)
31 | .HasColumnType("VARCHAR(180)")
32 | .HasColumnName("email");
33 |
34 | b.Property("Name")
35 | .IsRequired()
36 | .HasMaxLength(80)
37 | .HasColumnType("VARCHAR(80)")
38 | .HasColumnName("name");
39 |
40 | b.Property("Password")
41 | .IsRequired()
42 | .HasMaxLength(1000)
43 | .HasColumnType("VARCHAR(1000)")
44 | .HasColumnName("password");
45 |
46 | b.HasKey("Id");
47 |
48 | b.ToTable("User");
49 | });
50 | #pragma warning restore 612, 618
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Repositories/BaseRepository.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using Manager.Infra.Context;
3 | using System.Threading.Tasks;
4 | using Manager.Domain.Entities;
5 | using Manager.Infra.Interfaces;
6 | using Microsoft.EntityFrameworkCore;
7 | using System.Collections.Generic;
8 | using System.Linq.Expressions;
9 | using System;
10 |
11 | namespace Manager.Infra.Repositories{
12 | public class BaseRepository : IBaseRepository where T : Base{
13 | private readonly ManagerContext _context;
14 |
15 | public BaseRepository(ManagerContext context)
16 | {
17 | _context = context;
18 | }
19 |
20 | public virtual async Task CreateAsync(T obj){
21 | _context.Add(obj);
22 | await _context.SaveChangesAsync();
23 |
24 | return obj;
25 | }
26 |
27 | public virtual async Task UpdateAsync(T obj){
28 | _context.Entry(obj).State = EntityState.Modified;
29 | await _context.SaveChangesAsync();
30 |
31 | return obj;
32 | }
33 |
34 | public virtual async Task RemoveAsync(long id){
35 | var obj = await GetAsync(id);
36 |
37 | if(obj != null){
38 | _context.Remove(obj);
39 | await _context.SaveChangesAsync();
40 | }
41 | }
42 |
43 | public virtual async Task GetAsync(long id){
44 | var obj = await _context.Set()
45 | .AsNoTracking()
46 | .Where(x=>x.Id == id)
47 | .ToListAsync();
48 |
49 | return obj.FirstOrDefault();
50 | }
51 |
52 | public virtual async Task> GetAllAsync()
53 | {
54 | return await _context.Set()
55 | .AsNoTracking()
56 | .ToListAsync();
57 | }
58 |
59 | public virtual async Task GetAsync(
60 | Expression> expression,
61 | bool asNoTracking = true)
62 | => asNoTracking
63 | ? await BuildQuery(expression)
64 | .AsNoTracking()
65 | .FirstOrDefaultAsync()
66 |
67 | : await BuildQuery(expression)
68 | .AsNoTracking()
69 | .FirstOrDefaultAsync();
70 |
71 | public virtual async Task> SearchAsync(
72 | Expression> expression,
73 | bool asNoTracking = true)
74 | => asNoTracking
75 | ? await BuildQuery(expression)
76 | .AsNoTracking()
77 | .ToListAsync()
78 |
79 | : await BuildQuery(expression)
80 | .AsNoTracking()
81 | .ToListAsync();
82 |
83 | private IQueryable BuildQuery(Expression> expression)
84 | => _context.Set().Where(expression);
85 | }
86 | }
--------------------------------------------------------------------------------
/src/4 - Manager.Infra/Repositories/UserRepository.cs:
--------------------------------------------------------------------------------
1 | using Manager.Domain.Entities;
2 | using Manager.Infra.Context;
3 | using Manager.Infra.Interfaces;
4 |
5 | namespace Manager.Infra.Repositories
6 | {
7 | public class UserRepository : BaseRepository, IUserRepository{
8 | private readonly ManagerContext _context;
9 |
10 | public UserRepository(ManagerContext context) : base(context)
11 | {
12 | _context = context;
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/src/5 - Manager.Core/Communication/Handlers/DomainNotificationHandler.cs:
--------------------------------------------------------------------------------
1 | using Manager.Core.Communication.Messages.Notifications;
2 | using MediatR;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | namespace Manager.Core.Communication.Handlers
8 | {
9 | public class DomainNotificationHandler : INotificationHandler
10 | {
11 | private List _notifications;
12 |
13 | public IReadOnlyCollection Notifications
14 | => _notifications;
15 |
16 | public DomainNotificationHandler()
17 | {
18 | _notifications = new List();
19 | }
20 |
21 | public Task Handle(DomainNotification notification, CancellationToken cancellationToken = default)
22 | {
23 | _notifications.Add(notification);
24 | return Task.CompletedTask;
25 | }
26 |
27 | public bool HasNotifications()
28 | => _notifications.Count > 0;
29 |
30 | public int NotificationsCount()
31 | => _notifications.Count;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/5 - Manager.Core/Communication/Mediator/Interfaces/IMediatorHandler.cs:
--------------------------------------------------------------------------------
1 | using Manager.Core.Communication.Messages.Notifications;
2 | using System.Threading.Tasks;
3 |
4 | namespace Manager.Core.Communication.Mediator.Interfaces
5 | {
6 | public interface IMediatorHandler
7 | {
8 | Task PublishDomainNotificationAsync(T appNotification)
9 | where T : DomainNotification;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/5 - Manager.Core/Communication/Mediator/MediatorHandler.cs:
--------------------------------------------------------------------------------
1 | using Manager.Core.Communication.Mediator.Interfaces;
2 | using Manager.Core.Communication.Messages.Notifications;
3 | using MediatR;
4 | using System.Threading.Tasks;
5 |
6 | namespace Manager.Core.Communication.Mediator
7 | {
8 | public class MediatorHandler : IMediatorHandler
9 | {
10 | private readonly IMediator _mediator;
11 |
12 | public MediatorHandler(IMediator mediator)
13 | {
14 | _mediator = mediator;
15 | }
16 |
17 | public async Task PublishDomainNotificationAsync(T appNotification)
18 | where T : DomainNotification
19 | => await _mediator.Publish(appNotification);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/5 - Manager.Core/Communication/Messages/Notifications/DomainNotification.cs:
--------------------------------------------------------------------------------
1 | using Manager.Core.Enum;
2 |
3 | namespace Manager.Core.Communication.Messages.Notifications
4 | {
5 | public class DomainNotification : Notification
6 | {
7 | public string Message { get; private set; }
8 | public DomainNotificationType Type { get; private set; }
9 |
10 | public DomainNotification(string message, DomainNotificationType type)
11 | {
12 | Message = message;
13 | Type = type;
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/5 - Manager.Core/Communication/Messages/Notifications/Notification.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 | using System;
3 |
4 | namespace Manager.Core.Communication.Messages.Notifications
5 | {
6 | public abstract class Notification : INotification
7 | {
8 | public string Hash { get; private set; }
9 |
10 | public Notification()
11 | {
12 | Hash = Guid.NewGuid().ToString();
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/5 - Manager.Core/Enum/DomainNotificationType.cs:
--------------------------------------------------------------------------------
1 | namespace Manager.Core.Enum
2 | {
3 | public enum DomainNotificationType
4 | {
5 | UserAlreadyExists,
6 | UserInvalid,
7 | UserNotFound,
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/5 - Manager.Core/Manager.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/5 - Manager.Core/Structs/Optional.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Manager.Core.Structs
4 | {
5 | public struct Optional
6 | {
7 | public bool HasValue { get; private set; }
8 |
9 | private T _value;
10 | public T Value
11 | {
12 | get
13 | {
14 | if (HasValue)
15 | return _value;
16 | else
17 | throw new InvalidOperationException();
18 | }
19 | }
20 |
21 | public Optional(T value)
22 | {
23 | _value = value;
24 | HasValue = true;
25 | }
26 |
27 | public static explicit operator T(Optional optional)
28 | => optional.Value;
29 |
30 | public static implicit operator Optional(T value)
31 | => new Optional(value);
32 |
33 | public override bool Equals(object obj)
34 | {
35 | if (obj is Optional)
36 | return this.Equals((Optional)obj);
37 | else
38 | return false;
39 | }
40 | public bool Equals(Optional other)
41 | {
42 | if (HasValue && other.HasValue)
43 | return object.Equals(_value, other._value);
44 | else
45 | return HasValue == other.HasValue;
46 | }
47 |
48 | public static bool operator ==(Optional left, Optional right)
49 | => left.Equals(right);
50 |
51 | public static bool operator !=(Optional left, Optional right)
52 | => !(left == right);
53 |
54 | public override int GetHashCode()
55 | => base.GetHashCode();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/5 - Manager.Core/Validations/Message/ErrorMessages.cs:
--------------------------------------------------------------------------------
1 | namespace Manager.Core.Validations.Message
2 | {
3 | public static class ErrorMessages
4 | {
5 | public const string UserAlreadyExists
6 | = "Já existe um usuário cadastrado com o email informado.";
7 |
8 | public const string UserNotFound
9 | = "Não existe nenhum usuário com o id informado.";
10 |
11 | public static string UserInvalid(string errors)
12 | => "Os campos informados para o usuário estão inválidos" + errors;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/6 - Manager.Tests/Configurations/AutoMapper/AutoMapperConfiguration.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using Manager.API.ViewModes;
3 | using Manager.Domain.Entities;
4 | using Manager.Services.DTO;
5 |
6 | namespace Manager.Tests.Configurations.AutoMapper
7 | {
8 | public static class AutoMapperConfiguration
9 | {
10 | public static IMapper GetConfiguration()
11 | {
12 | var autoMapperConfig = new MapperConfiguration(cfg =>
13 | {
14 | cfg.CreateMap()
15 | .ReverseMap();
16 |
17 | cfg.CreateMap()
18 | .ReverseMap();
19 |
20 | cfg.CreateMap()
21 | .ReverseMap();
22 | });
23 |
24 | return autoMapperConfig.CreateMapper();
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/6 - Manager.Tests/Fixtures/UserFixture.cs:
--------------------------------------------------------------------------------
1 | using Bogus;
2 | using Bogus.DataSets;
3 | using Manager.Domain.Entities;
4 | using Manager.Services.DTO;
5 | using System.Collections.Generic;
6 |
7 | namespace Manager.Tests.Fixtures
8 | {
9 | public class UserFixture
10 | {
11 | public static User CreateValidUser()
12 | {
13 | return new User(
14 | name: new Name().FirstName(),
15 | email: new Internet().Email(),
16 | password: new Internet().Password());
17 | }
18 |
19 | public static List CreateListValidUser(int limit = 5)
20 | {
21 | var list = new List();
22 |
23 | for (int i = 0; i < limit; i++)
24 | list.Add(CreateValidUser());
25 |
26 | return list;
27 | }
28 |
29 | public static UserDTO CreateValidUserDTO(bool newId = false)
30 | {
31 | return new UserDTO
32 | {
33 | Id = newId ? new Randomizer().Int(0, 1000) : 0,
34 | Name = new Name().FirstName(),
35 | Email = new Internet().Email(),
36 | Password = new Internet().Password()
37 | };
38 | }
39 |
40 | public static UserDTO CreateInvalidUserDTO()
41 | {
42 | return new UserDTO
43 | {
44 | Id = 0,
45 | Name = "",
46 | Email = "",
47 | Password = ""
48 | };
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/6 - Manager.Tests/Manager.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | runtime; build; native; contentfiles; analyzers; buildtransitive
17 | all
18 |
19 |
20 | runtime; build; native; contentfiles; analyzers; buildtransitive
21 | all
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/6 - Manager.Tests/Projects/Services/UserServiceTests.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using Bogus;
3 | using Bogus.DataSets;
4 | using EscNet.Cryptography.Interfaces;
5 | using FluentAssertions;
6 | using Manager.Core.Communication.Mediator.Interfaces;
7 | using Manager.Domain.Entities;
8 | using Manager.Infra.Interfaces;
9 | using Manager.Services.DTO;
10 | using Manager.Services.Interfaces;
11 | using Manager.Services.Services;
12 | using Manager.Tests.Configurations.AutoMapper;
13 | using Manager.Tests.Fixtures;
14 | using Moq;
15 | using System;
16 | using System.Collections.Generic;
17 | using System.Threading.Tasks;
18 | using Xunit;
19 | using System.Linq.Expressions;
20 |
21 | namespace Manager.Tests.Projects.Services
22 | {
23 | public class UserServiceTests
24 | {
25 | // Subject Under Test (Quem será testado!)
26 | private readonly IUserService _sut;
27 |
28 | //Mocks
29 | private readonly IMapper _mapper;
30 | private readonly Mock _userRepositoryMock;
31 | private readonly Mock _rijndaelCryptographyMock;
32 | private readonly Mock _mediatorHandler;
33 |
34 | public UserServiceTests()
35 | {
36 | _mapper = AutoMapperConfiguration.GetConfiguration();
37 | _userRepositoryMock = new Mock();
38 | _rijndaelCryptographyMock = new Mock();
39 | _mediatorHandler = new Mock();
40 |
41 | _sut = new UserService(
42 | mapper: _mapper,
43 | userRepository: _userRepositoryMock.Object,
44 | rijndaelCryptography: _rijndaelCryptographyMock.Object,
45 | mediator: _mediatorHandler.Object);
46 | }
47 |
48 | #region Create
49 |
50 | [Fact(DisplayName = "Create Valid User")]
51 | [Trait("Category", "Services")]
52 | public async Task Create_WhenUserIsValid_ReturnsUserDTO()
53 | {
54 | // Arrange
55 | var userToCreate = UserFixture.CreateValidUserDTO();
56 |
57 | var encryptedPassword = new Lorem().Sentence();
58 | var userCreated = _mapper.Map(userToCreate);
59 | userCreated.SetPassword(encryptedPassword);
60 |
61 | _userRepositoryMock.Setup(x => x.GetAsync(
62 | It.IsAny>>(),
63 | It.IsAny()))
64 | .ReturnsAsync(() => null);
65 |
66 | _rijndaelCryptographyMock.Setup(x => x.Encrypt(It.IsAny()))
67 | .Returns(encryptedPassword);
68 |
69 | _userRepositoryMock.Setup(x => x.CreateAsync(It.IsAny()))
70 | .ReturnsAsync(() => userCreated);
71 |
72 | // Act
73 | var result = await _sut.CreateAsync(userToCreate);
74 |
75 | // Assert
76 | result.Value.Should()
77 | .BeEquivalentTo(_mapper.Map(userCreated));
78 | }
79 |
80 | [Fact(DisplayName = "Create When User Exists")]
81 | [Trait("Category", "Services")]
82 | public async Task Create_WhenUserExists_ReturnsEmptyOptional()
83 | {
84 | // Arrange
85 | var userToCreate = UserFixture.CreateValidUserDTO();
86 | var userExists = UserFixture.CreateValidUser();
87 |
88 | _userRepositoryMock.Setup(x => x.GetAsync(
89 | It.IsAny>>(),
90 | It.IsAny()))
91 | .ReturnsAsync(() => userExists);
92 |
93 | // Act
94 | var result = await _sut.CreateAsync(userToCreate);
95 |
96 |
97 | // Act
98 | result.HasValue.Should()
99 | .BeFalse();
100 | }
101 |
102 | [Fact(DisplayName = "Create When User is Invalid")]
103 | [Trait("Category", "Services")]
104 | public async Task Create_WhenUserIsInvalid_ReturnsEmptyOptional()
105 | {
106 | // Arrange
107 | var userToCreate = UserFixture.CreateInvalidUserDTO();
108 |
109 | _userRepositoryMock.Setup(x => x.GetAsync(
110 | It.IsAny>>(),
111 | It.IsAny()))
112 | .ReturnsAsync(() => null);
113 |
114 | // Act
115 | var result = await _sut.CreateAsync(userToCreate);
116 |
117 |
118 | // Act
119 | result.HasValue.Should()
120 | .BeFalse();
121 | }
122 |
123 | #endregion
124 |
125 | #region Update
126 |
127 | [Fact(DisplayName = "Update Valid User")]
128 | [Trait("Category", "Services")]
129 | public async Task Update_WhenUserIsValid_ReturnsUserDTO()
130 | {
131 | // Arrange
132 | var oldUser = UserFixture.CreateValidUser();
133 | var userToUpdate = UserFixture.CreateValidUserDTO();
134 | var userUpdated = _mapper.Map(userToUpdate);
135 |
136 | var encryptedPassword = new Lorem().Sentence();
137 |
138 | _userRepositoryMock.Setup(x => x.GetAsync(oldUser.Id))
139 | .ReturnsAsync(() => oldUser);
140 |
141 | _rijndaelCryptographyMock.Setup(x => x.Encrypt(It.IsAny()))
142 | .Returns(encryptedPassword);
143 |
144 | _userRepositoryMock.Setup(x => x.UpdateAsync(It.IsAny()))
145 | .ReturnsAsync(() => userUpdated);
146 |
147 | // Act
148 | var result = await _sut.UpdateAsync(userToUpdate);
149 |
150 | // Assert
151 | result.Value.Should()
152 | .BeEquivalentTo(_mapper.Map(userUpdated));
153 | }
154 |
155 | [Fact(DisplayName = "Update When User Not Exists")]
156 | [Trait("Category", "Services")]
157 | public async Task Update_WhenUserNotExists_ReturnsEmptyOptional()
158 | {
159 | // Arrange
160 | var userToUpdate = UserFixture.CreateValidUserDTO();
161 |
162 | _userRepositoryMock.Setup(x => x.GetAsync(
163 | It.IsAny>>(),
164 | It.IsAny()))
165 | .ReturnsAsync(() => null);
166 |
167 | // Act
168 | var result = await _sut.UpdateAsync(userToUpdate);
169 |
170 | // Act
171 | result.HasValue.Should()
172 | .BeFalse();
173 | }
174 |
175 | [Fact(DisplayName = "Update When User is Invalid")]
176 | [Trait("Category", "Services")]
177 | public async Task Update_WhenUserIsInvalid_ReturnsEmptyOptional()
178 | {
179 | // Arrange
180 | var oldUser = UserFixture.CreateValidUser();
181 | var userToUpdate = UserFixture.CreateInvalidUserDTO();
182 |
183 | _userRepositoryMock.Setup(x => x.GetAsync(
184 | It.IsAny>>(),
185 | It.IsAny()))
186 | .ReturnsAsync(() => oldUser);
187 |
188 | // Act
189 | var result = await _sut.UpdateAsync(userToUpdate);
190 |
191 |
192 | // Act
193 | result.HasValue.Should()
194 | .BeFalse();
195 | }
196 |
197 | #endregion
198 |
199 | #region Remove
200 |
201 | [Fact(DisplayName = "Remove User")]
202 | [Trait("Category", "Services")]
203 | public async Task Remove_WhenUserExists_RemoveUser()
204 | {
205 | // Arrange
206 | var userId = new Randomizer().Int(0, 1000);
207 |
208 | _userRepositoryMock.Setup(x => x.RemoveAsync(It.IsAny()))
209 | .Verifiable();
210 |
211 | // Act
212 | await _sut.RemoveAsync(userId);
213 |
214 | // Assert
215 | _userRepositoryMock.Verify(x => x.RemoveAsync(userId), Times.Once);
216 | }
217 |
218 | #endregion
219 |
220 | #region Get
221 |
222 | [Fact(DisplayName = "Get By Id")]
223 | [Trait("Category", "Services")]
224 | public async Task GetById_WhenUserExists_ReturnsUserDTO()
225 | {
226 | // Arrange
227 | var userId = new Randomizer().Int(0, 1000);
228 | var userFound = UserFixture.CreateValidUser();
229 |
230 | _userRepositoryMock.Setup(x => x.GetAsync(userId))
231 | .ReturnsAsync(() => userFound);
232 |
233 | // Act
234 | var result = await _sut.GetAsync(userId);
235 |
236 | // Assert
237 | result.Value.Should()
238 | .BeEquivalentTo(_mapper.Map(userFound));
239 | }
240 |
241 | [Fact(DisplayName = "Get By Id When User Not Exists")]
242 | [Trait("Category", "Services")]
243 | public async Task GetById_WhenUserNotExists_ReturnsEmptyOptional()
244 | {
245 | // Arrange
246 | var userId = new Randomizer().Int(0, 1000);
247 |
248 | _userRepositoryMock.Setup(x => x.GetAsync(userId))
249 | .ReturnsAsync(() => null);
250 |
251 | // Act
252 | var result = await _sut.GetAsync(userId);
253 |
254 | // Assert
255 | result.Value.Should()
256 | .BeNull();
257 | }
258 |
259 | [Fact(DisplayName = "Get By Email")]
260 | [Trait("Category", "Services")]
261 | public async Task GetByEmail_WhenUserExists_ReturnsUserDTO()
262 | {
263 | // Arrange
264 | var userEmail = new Internet().Email();
265 | var userFound = UserFixture.CreateValidUser();
266 |
267 | _userRepositoryMock.Setup(x => x.GetAsync(
268 | It.IsAny>>(),
269 | It.IsAny()))
270 | .ReturnsAsync(() => userFound);
271 |
272 | // Act
273 | var result = await _sut.GetByEmailAsync(userEmail);
274 |
275 | // Assert
276 | result.Value.Should()
277 | .BeEquivalentTo(_mapper.Map(userFound));
278 | }
279 |
280 | [Fact(DisplayName = "Get By Email When User Not Exists")]
281 | [Trait("Category", "Services")]
282 | public async Task GetByEmail_WhenUserNotExists_ReturnsEmptyOptional()
283 | {
284 | // Arrange
285 | var userEmail = new Internet().Email();
286 |
287 | _userRepositoryMock.Setup(x => x.GetAsync(
288 | It.IsAny>>(),
289 | It.IsAny()))
290 | .ReturnsAsync(() => null);
291 |
292 | // Act
293 | var result = await _sut.GetByEmailAsync(userEmail);
294 |
295 | // Assert
296 | result.Value.Should()
297 | .BeNull();
298 | }
299 |
300 | [Fact(DisplayName = "Get All Users")]
301 | [Trait("Category", "Services")]
302 | public async Task GetAllUsers_WhenUsersExists_ReturnsAListOfUserDTO()
303 | {
304 | // Arrange
305 | var usersFound = UserFixture.CreateListValidUser();
306 |
307 | _userRepositoryMock.Setup(x => x.GetAllAsync())
308 | .ReturnsAsync(() => usersFound);
309 |
310 | // Act
311 | var result = await _sut.GetAllAsync();
312 |
313 | // Assert
314 | result.Value.Should()
315 | .BeEquivalentTo(_mapper.Map>(usersFound));
316 | }
317 |
318 | [Fact(DisplayName = "Get All Users When None User Found")]
319 | [Trait("Category", "Services")]
320 | public async Task GetAllUsers_WhenNoneUserFound_ReturnsEmptyList()
321 | {
322 | // Arrange
323 |
324 | _userRepositoryMock.Setup(x => x.GetAllAsync())
325 | .ReturnsAsync(() => null);
326 |
327 | // Act
328 | var result = await _sut.GetAllAsync();
329 |
330 | // Assert
331 | result.Value.Should()
332 | .BeEmpty();
333 | }
334 |
335 | #endregion
336 |
337 | #region Search
338 |
339 | [Fact(DisplayName = "Search By Name")]
340 | [Trait("Category", "Services")]
341 | public async Task SearchByName_WhenAnyUserFound_ReturnsAListOfUserDTO()
342 | {
343 | // Arrange
344 | var nameToSearch = new Name().FirstName();
345 | var usersFound = UserFixture.CreateListValidUser();
346 |
347 | _userRepositoryMock.Setup(x => x.SearchAsync(
348 | It.IsAny>>(),
349 | It.IsAny()))
350 | .ReturnsAsync(() => usersFound);
351 |
352 | // Act
353 | var result = await _sut.SearchByNameAsync(nameToSearch);
354 |
355 | // Assert
356 | result.Value.Should()
357 | .BeEquivalentTo(_mapper.Map>(usersFound));
358 | }
359 |
360 | [Fact(DisplayName = "Search By Name When None User Found")]
361 | [Trait("Category", "Services")]
362 | public async Task SearchByName_WhenNoneUserFound_ReturnsEmptyList()
363 | {
364 | // Arrange
365 | var nameToSearch = new Name().FirstName();
366 |
367 | _userRepositoryMock.Setup(x => x.SearchAsync(
368 | It.IsAny>>(),
369 | It.IsAny()))
370 | .ReturnsAsync(() => null);
371 |
372 | // Act
373 | var result = await _sut.SearchByNameAsync(nameToSearch);
374 |
375 | // Assert
376 | result.Value.Should()
377 | .BeEmpty();
378 | }
379 |
380 | [Fact(DisplayName = "Search By Email")]
381 | [Trait("Category", "Services")]
382 | public async Task SearchByEmail_WhenAnyUserFound_ReturnsAListOfUserDTO()
383 | {
384 | // Arrange
385 | var emailSoSearch = new Internet().Email();
386 | var usersFound = UserFixture.CreateListValidUser();
387 |
388 | _userRepositoryMock.Setup(x => x.SearchAsync(
389 | It.IsAny>>(),
390 | It.IsAny()))
391 | .ReturnsAsync(() => usersFound);
392 |
393 | // Act
394 | var result = await _sut.SearchByEmailAsync(emailSoSearch);
395 |
396 | // Assert
397 | result.Value.Should()
398 | .BeEquivalentTo(_mapper.Map>(usersFound));
399 | }
400 |
401 | [Fact(DisplayName = "Search By Email When None User Found")]
402 | [Trait("Category", "Services")]
403 | public async Task SearchByEmail_WhenNoneUserFound_ReturnsEmptyList()
404 | {
405 | // Arrange
406 | var emailSoSearch = new Internet().Email();
407 |
408 | _userRepositoryMock.Setup(x => x.SearchAsync(
409 | It.IsAny>>(),
410 | It.IsAny()))
411 | .ReturnsAsync(() => null);
412 |
413 | // Act
414 | var result = await _sut.SearchByEmailAsync(emailSoSearch);
415 |
416 | // Assert
417 | result.Value.Should()
418 | .BeEmpty();
419 | }
420 |
421 | #endregion
422 | }
423 | }
424 |
--------------------------------------------------------------------------------