├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── Api ├── .vscode │ ├── launch.json │ └── tasks.json ├── Api.csproj ├── Data │ ├── BidEntity.cs │ ├── BidRepository.cs │ ├── HouseDbContext.cs │ ├── HouseEntity.cs │ ├── HouseRepository.cs │ └── SeedData.cs ├── Dtos │ ├── BidDto.cs │ ├── HouseDetailDto.cs │ └── HouseDto.cs ├── Migrations │ ├── 20220311103107_Initial.Designer.cs │ ├── 20220311103107_Initial.cs │ ├── 20220314113837_photo.Designer.cs │ ├── 20220314113837_photo.cs │ └── HouseDbContextModelSnapshot.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── WebApplicationBidExtensions.cs ├── WebApplicationHouseExtensions.cs ├── appsettings.Development.json └── appsettings.json ├── HouseExampleImages ├── 164558.jpeg ├── 259600.jpeg ├── 277667.jpeg ├── 462358.jpeg └── 534182.jpeg ├── ReactWeb ├── .eslintrc.cjs ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── src │ ├── ValidationSummary.tsx │ ├── apiStatus.tsx │ ├── bids │ │ └── Bids.tsx │ ├── config.ts │ ├── hooks │ │ ├── BidHooks.ts │ │ └── HouseHooks.ts │ ├── house │ │ ├── HouseAdd.tsx │ │ ├── HouseDetail.tsx │ │ ├── HouseEdit.tsx │ │ ├── HouseForm.tsx │ │ ├── HouseList.tsx │ │ └── defaultPhoto.ts │ ├── index.css │ ├── main.tsx │ ├── main │ │ ├── App.tsx │ │ ├── GloboLogo.png │ │ ├── Header.tsx │ │ └── app.css │ ├── toBase64.ts │ ├── types │ │ ├── bid.ts │ │ ├── house.ts │ │ └── problem.ts │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts └── ps-globomantics-webapi-react.sln /.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 | /build 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | [Ww][Ii][Nn]32/ 28 | [Aa][Rr][Mm]/ 29 | [Aa][Rr][Mm]64/ 30 | bld/ 31 | [Bb]in/ 32 | [Oo]bj/ 33 | [Oo]ut/ 34 | [Ll]og/ 35 | [Ll]ogs/ 36 | 37 | # Visual Studio 2015/2017 cache/options directory 38 | .vs/ 39 | # Uncomment if you have tasks that create the project's static files in wwwroot 40 | #wwwroot/ 41 | 42 | # Visual Studio 2017 auto generated files 43 | Generated\ Files/ 44 | 45 | # MSTest test Results 46 | [Tt]est[Rr]esult*/ 47 | [Bb]uild[Ll]og.* 48 | 49 | # NUnit 50 | *.VisualState.xml 51 | TestResult.xml 52 | nunit-*.xml 53 | 54 | # Build Results of an ATL Project 55 | [Dd]ebugPS/ 56 | [Rr]eleasePS/ 57 | dlldata.c 58 | 59 | # Benchmark Results 60 | BenchmarkDotNet.Artifacts/ 61 | 62 | # .NET Core 63 | project.lock.json 64 | project.fragment.lock.json 65 | artifacts/ 66 | 67 | # ASP.NET Scaffolding 68 | ScaffoldingReadMe.txt 69 | 70 | # StyleCop 71 | StyleCopReport.xml 72 | 73 | # Files built by Visual Studio 74 | *_i.c 75 | *_p.c 76 | *_h.h 77 | *.ilk 78 | *.meta 79 | *.obj 80 | *.iobj 81 | *.pch 82 | *.pdb 83 | *.ipdb 84 | *.pgc 85 | *.pgd 86 | *.rsp 87 | *.sbr 88 | *.tlb 89 | *.tli 90 | *.tlh 91 | *.tmp 92 | *.tmp_proj 93 | *_wpftmp.csproj 94 | *.log 95 | *.vspscc 96 | *.vssscc 97 | .builds 98 | *.pidb 99 | *.svclog 100 | *.scc 101 | 102 | # Chutzpah Test files 103 | _Chutzpah* 104 | 105 | # Visual C++ cache files 106 | ipch/ 107 | *.aps 108 | *.ncb 109 | *.opendb 110 | *.opensdf 111 | *.sdf 112 | *.cachefile 113 | *.VC.db 114 | *.VC.VC.opendb 115 | 116 | # Visual Studio profiler 117 | *.psess 118 | *.vsp 119 | *.vspx 120 | *.sap 121 | 122 | # Visual Studio Trace Files 123 | *.e2e 124 | 125 | # TFS 2012 Local Workspace 126 | $tf/ 127 | 128 | # Guidance Automation Toolkit 129 | *.gpState 130 | 131 | # ReSharper is a .NET coding add-in 132 | _ReSharper*/ 133 | *.[Rr]e[Ss]harper 134 | *.DotSettings.user 135 | 136 | # TeamCity is a build add-in 137 | _TeamCity* 138 | 139 | # DotCover is a Code Coverage Tool 140 | *.dotCover 141 | 142 | # AxoCover is a Code Coverage Tool 143 | .axoCover/* 144 | !.axoCover/settings.json 145 | 146 | # Coverlet is a free, cross platform Code Coverage Tool 147 | coverage*.json 148 | coverage*.xml 149 | coverage*.info 150 | 151 | # Visual Studio code coverage results 152 | *.coverage 153 | *.coveragexml 154 | 155 | # NCrunch 156 | _NCrunch_* 157 | .*crunch*.local.xml 158 | nCrunchTemp_* 159 | 160 | # MightyMoose 161 | *.mm.* 162 | AutoTest.Net/ 163 | 164 | # Web workbench (sass) 165 | .sass-cache/ 166 | 167 | # Installshield output folder 168 | [Ee]xpress/ 169 | 170 | # DocProject is a documentation generator add-in 171 | DocProject/buildhelp/ 172 | DocProject/Help/*.HxT 173 | DocProject/Help/*.HxC 174 | DocProject/Help/*.hhc 175 | DocProject/Help/*.hhk 176 | DocProject/Help/*.hhp 177 | DocProject/Help/Html2 178 | DocProject/Help/html 179 | 180 | # Click-Once directory 181 | publish/ 182 | 183 | # Publish Web Output 184 | *.[Pp]ublish.xml 185 | *.azurePubxml 186 | # Note: Comment the next line if you want to checkin your web deploy settings, 187 | # but database connection strings (with potential passwords) will be unencrypted 188 | *.pubxml 189 | *.publishproj 190 | 191 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 192 | # checkin your Azure Web App publish settings, but sensitive information contained 193 | # in these scripts will be unencrypted 194 | PublishScripts/ 195 | 196 | # NuGet Packages 197 | *.nupkg 198 | # NuGet Symbol Packages 199 | *.snupkg 200 | # The packages folder can be ignored because of Package Restore 201 | **/[Pp]ackages/* 202 | # except build/, which is used as an MSBuild target. 203 | !**/[Pp]ackages/build/ 204 | # Uncomment if necessary however generally it will be regenerated when needed 205 | #!**/[Pp]ackages/repositories.config 206 | # NuGet v3's project.json files produces more ignorable files 207 | *.nuget.props 208 | *.nuget.targets 209 | 210 | # Microsoft Azure Build Output 211 | csx/ 212 | *.build.csdef 213 | 214 | # Microsoft Azure Emulator 215 | ecf/ 216 | rcf/ 217 | 218 | # Windows Store app package directories and files 219 | AppPackages/ 220 | BundleArtifacts/ 221 | Package.StoreAssociation.xml 222 | _pkginfo.txt 223 | *.appx 224 | *.appxbundle 225 | *.appxupload 226 | 227 | # Visual Studio cache files 228 | # files ending in .cache can be ignored 229 | *.[Cc]ache 230 | # but keep track of directories ending in .cache 231 | !?*.[Cc]ache/ 232 | 233 | # Others 234 | ClientBin/ 235 | ~$* 236 | *~ 237 | *.dbmdl 238 | *.dbproj.schemaview 239 | *.jfm 240 | *.pfx 241 | *.publishsettings 242 | orleans.codegen.cs 243 | 244 | # Including strong name files can present a security risk 245 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 246 | #*.snk 247 | 248 | # Since there are multiple workflows, uncomment next line to ignore bower_components 249 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 250 | #bower_components/ 251 | 252 | # RIA/Silverlight projects 253 | Generated_Code/ 254 | 255 | # Backup & report files from converting an old project file 256 | # to a newer Visual Studio version. Backup files are not needed, 257 | # because we have git ;-) 258 | _UpgradeReport_Files/ 259 | Backup*/ 260 | UpgradeLog*.XML 261 | UpgradeLog*.htm 262 | ServiceFabricBackup/ 263 | *.rptproj.bak 264 | 265 | # SQL Server files 266 | *.mdf 267 | *.ldf 268 | *.ndf 269 | 270 | # Business Intelligence projects 271 | *.rdl.data 272 | *.bim.layout 273 | *.bim_*.settings 274 | *.rptproj.rsuser 275 | *- [Bb]ackup.rdl 276 | *- [Bb]ackup ([0-9]).rdl 277 | *- [Bb]ackup ([0-9][0-9]).rdl 278 | 279 | # Microsoft Fakes 280 | FakesAssemblies/ 281 | 282 | # GhostDoc plugin setting file 283 | *.GhostDoc.xml 284 | 285 | # Node.js Tools for Visual Studio 286 | .ntvs_analysis.dat 287 | node_modules/ 288 | 289 | # Visual Studio 6 build log 290 | *.plg 291 | 292 | # Visual Studio 6 workspace options file 293 | *.opt 294 | 295 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 296 | *.vbw 297 | 298 | # Visual Studio LightSwitch build output 299 | **/*.HTMLClient/GeneratedArtifacts 300 | **/*.DesktopClient/GeneratedArtifacts 301 | **/*.DesktopClient/ModelManifest.xml 302 | **/*.Server/GeneratedArtifacts 303 | **/*.Server/ModelManifest.xml 304 | _Pvt_Extensions 305 | 306 | # Paket dependency manager 307 | .paket/paket.exe 308 | paket-files/ 309 | 310 | # FAKE - F# Make 311 | .fake/ 312 | 313 | # CodeRush personal settings 314 | .cr/personal 315 | 316 | # Python Tools for Visual Studio (PTVS) 317 | __pycache__/ 318 | *.pyc 319 | 320 | # Cake - Uncomment if you are using it 321 | # tools/** 322 | # !tools/packages.config 323 | 324 | # Tabs Studio 325 | *.tss 326 | 327 | # Telerik's JustMock configuration file 328 | *.jmconfig 329 | 330 | # BizTalk build output 331 | *.btp.cs 332 | *.btm.cs 333 | *.odx.cs 334 | *.xsd.cs 335 | 336 | # OpenCover UI analysis results 337 | OpenCover/ 338 | 339 | # Azure Stream Analytics local run output 340 | ASALocalRun/ 341 | 342 | # MSBuild Binary and Structured Log 343 | *.binlog 344 | 345 | # NVidia Nsight GPU debugger configuration file 346 | *.nvuser 347 | 348 | # MFractors (Xamarin productivity tool) working folder 349 | .mfractor/ 350 | 351 | # Local History for Visual Studio 352 | .localhistory/ 353 | 354 | # BeatPulse healthcheck temp database 355 | healthchecksdb 356 | 357 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 358 | MigrationBackup/ 359 | 360 | # Ionide (cross platform F# VS Code tools) working folder 361 | .ionide/ 362 | 363 | # Fody - auto-generated XML schema 364 | FodyWeavers.xsd -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | // Use IntelliSense to find out which attributes exist for C# debugging 6 | // Use hover for the description of the existing attributes 7 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/Api/bin/Debug/net6.0/Api.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/Api", 16 | "stopAtEntry": false, 17 | // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser 18 | "serverReadyAction": { 19 | "action": "openExternally", 20 | "pattern": "\\bNow listening on:\\s+(https?://\\S+)" 21 | }, 22 | "env": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "sourceFileMap": { 26 | "/Views": "${workspaceFolder}/Views" 27 | } 28 | }, 29 | { 30 | "name": ".NET Core Attach", 31 | "type": "coreclr", 32 | "request": "attach" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } 4 | -------------------------------------------------------------------------------- /.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}/Api/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}/Api/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 | "--project", 36 | "${workspaceFolder}/Api/Api.csproj" 37 | ], 38 | "problemMatcher": "$msCompile" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /Api/.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}/bin/Debug/net6.0/Api.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}", 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 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /Api/.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}/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}/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 | "--project", 36 | "${workspaceFolder}/Api.csproj" 37 | ], 38 | "problemMatcher": "$msCompile" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /Api/Api.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Api/Data/BidEntity.cs: -------------------------------------------------------------------------------- 1 | public class BidEntity 2 | { 3 | public int Id { get; set; } 4 | public int HouseId { get; set; } 5 | public HouseEntity? House { get; set; } 6 | public string Bidder { get; set; } = string.Empty; 7 | public int Amount { get; set; } 8 | } -------------------------------------------------------------------------------- /Api/Data/BidRepository.cs: -------------------------------------------------------------------------------- 1 | public interface IBidRepository 2 | { 3 | Task> Get(int houseId); 4 | Task Add(BidDto bid); 5 | } 6 | 7 | public class BidRepository: IBidRepository 8 | { 9 | private readonly HouseDbContext context; 10 | 11 | public BidRepository(HouseDbContext context) 12 | { 13 | this.context = context; 14 | } 15 | 16 | public async Task> Get(int houseId) 17 | { 18 | return await context.Bids.Where(b => b.HouseId == houseId) 19 | .Select(b => new BidDto(b.Id, b.HouseId, b.Bidder, b.Amount)) 20 | .ToListAsync(); 21 | } 22 | 23 | public async Task Add(BidDto dto) 24 | { 25 | var entity = new BidEntity(); 26 | entity.HouseId = dto.HouseId; 27 | entity.Bidder = dto.Bidder; 28 | entity.Amount = dto.Amount; 29 | context.Bids.Add(entity); 30 | await context.SaveChangesAsync(); 31 | return new BidDto(entity.Id, entity.HouseId, 32 | entity.Bidder, entity.Amount); 33 | } 34 | } -------------------------------------------------------------------------------- /Api/Data/HouseDbContext.cs: -------------------------------------------------------------------------------- 1 | public class HouseDbContext : DbContext 2 | { 3 | public HouseDbContext(DbContextOptions options) : base(options) { } 4 | public DbSet Houses => Set(); 5 | public DbSet Bids => Set(); 6 | 7 | protected override void OnConfiguring(DbContextOptionsBuilder options) 8 | { 9 | var folder = Environment.SpecialFolder.LocalApplicationData; 10 | var path = Environment.GetFolderPath(folder); 11 | options.UseSqlite($"Data Source={Path.Join(path, "houses.db")}"); 12 | } 13 | 14 | protected override void OnModelCreating(ModelBuilder modelBuilder) 15 | { 16 | SeedData.Seed(modelBuilder); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Api/Data/HouseEntity.cs: -------------------------------------------------------------------------------- 1 | public class HouseEntity 2 | { 3 | public int Id { get; set; } 4 | public string? Address { get; set; } 5 | public string? Country { get; set; } 6 | public string? Description { get; set; } 7 | public int Price { get; set; } 8 | public string? Photo { get; set; } 9 | } -------------------------------------------------------------------------------- /Api/Data/HouseRepository.cs: -------------------------------------------------------------------------------- 1 | public interface IHouseRepository 2 | { 3 | Task> GetAll(); 4 | Task Get(int id); 5 | Task Add(HouseDetailDto house); 6 | Task Update(HouseDetailDto house); 7 | Task Delete(int id); 8 | } 9 | 10 | public class HouseRepository : IHouseRepository 11 | { 12 | private readonly HouseDbContext context; 13 | 14 | private static HouseDetailDto EntityToDetailDto(HouseEntity e) 15 | { 16 | return new HouseDetailDto(e.Id, e.Address, e.Country, e.Description, e.Price, e.Photo); 17 | } 18 | 19 | private static void DtoToEntity(HouseDetailDto dto, HouseEntity e) 20 | { 21 | e.Address = dto.Address; 22 | e.Country = dto.Country; 23 | e.Description = dto.Description; 24 | e.Price = dto.Price; 25 | e.Photo = dto.Photo; 26 | } 27 | 28 | public HouseRepository(HouseDbContext context) 29 | { 30 | this.context = context; 31 | } 32 | 33 | public async Task> GetAll() 34 | { 35 | return await context.Houses.Select(e => new HouseDto(e.Id, e.Address, e.Country, e.Price)).ToListAsync(); 36 | } 37 | 38 | public async Task Get(int id) 39 | { 40 | var entity = await context.Houses.SingleOrDefaultAsync(h => h.Id == id); 41 | if (entity == null) 42 | return null; 43 | return EntityToDetailDto(entity); 44 | } 45 | 46 | public async Task Add(HouseDetailDto dto) 47 | { 48 | var entity = new HouseEntity(); 49 | DtoToEntity(dto, entity); 50 | context.Houses.Add(entity); 51 | await context.SaveChangesAsync(); 52 | return EntityToDetailDto(entity); 53 | } 54 | 55 | public async Task Update(HouseDetailDto dto) 56 | { 57 | var entity = await context.Houses.FindAsync(dto.Id); 58 | if (entity == null) 59 | throw new ArgumentException($"Trying to update house: entity with ID {dto.Id} not found."); 60 | DtoToEntity(dto, entity); 61 | context.Entry(entity).State = EntityState.Modified; 62 | await context.SaveChangesAsync(); 63 | return EntityToDetailDto(entity); 64 | } 65 | 66 | public async Task Delete(int id) 67 | { 68 | var entity = await context.Houses.FindAsync(id); 69 | if (entity == null) 70 | throw new ArgumentException($"Trying to delete house: entity with ID {id} not found."); 71 | context.Houses.Remove(entity); 72 | await context.SaveChangesAsync(); 73 | } 74 | } -------------------------------------------------------------------------------- /Api/Data/SeedData.cs: -------------------------------------------------------------------------------- 1 | public static class SeedData 2 | { 3 | public static void Seed(ModelBuilder builder) 4 | { 5 | builder.Entity().HasData(new List { 6 | new HouseEntity { 7 | Id = 1, 8 | Address = "12 Valley of Kings, Geneva", 9 | Country = "Switzerland", 10 | Description = "A superb detached Victorian property on one of the town's finest roads, within easy reach of Barnes Village. The property has in excess of 6000 sq/ft of accommodation, a driveway and landscaped garden.", 11 | Price = 900000 12 | }, 13 | new HouseEntity 14 | { 15 | Id = 2, 16 | Address = "89 Road of Forks, Bern", 17 | Country = "Switzerland", 18 | Description = "This impressive family home, which dates back to approximately 1840, offers original period features throughout and is set back from the road with off street parking for up to six cars and an original Coach House, which has been incorporated into the main house to provide further accommodation. ", 19 | Price = 500000 20 | }, 21 | new HouseEntity 22 | { 23 | Id = 3, 24 | Address = "Grote Hof 12, Amsterdam", 25 | Country = "The Netherlands", 26 | Description = "This house has been designed and built to an impeccable standard offering luxurious and elegant living. The accommodation is arranged over four floors comprising a large entrance hall, living room with tall sash windows, dining room, study and WC on the ground floor.", 27 | Price = 200500 28 | }, 29 | new HouseEntity 30 | { 31 | Id = 4, 32 | Address = "Meel Kade 321, The Hague", 33 | Country = "The Netherlands", 34 | Description = "Discreetly situated a unique two storey period home enviably located on the corner of Krom Road and Recht Road offering seclusion and privacy. The house features a magnificent double height reception room with doors leading directly out onto a charming courtyard garden.", 35 | Price = 259500 36 | }, 37 | new HouseEntity 38 | { 39 | Id = 5, 40 | Address = "Oude Gracht 32, Utrecht", 41 | Country = "The Netherlands", 42 | Description = "This luxurious three bedroom flat is contemporary in style and benefits from the use of a gymnasium and a reserved underground parking space.", 43 | Price = 400500 44 | } 45 | }); 46 | builder.Entity().HasData(new List 47 | { 48 | new BidEntity { Id = 1, HouseId = 1, Bidder = "Sonia Reading", Amount = 200000 }, 49 | new BidEntity { Id = 2, HouseId = 1, Bidder = "Dick Johnson", Amount = 202400 }, 50 | new BidEntity { Id = 3, HouseId = 2, Bidder = "Mohammed Vahls", Amount = 302400 }, 51 | new BidEntity { Id = 4, HouseId = 2, Bidder = "Jane Williams", Amount = 310500 }, 52 | new BidEntity { Id = 5, HouseId = 2, Bidder = "John Kepler", Amount = 315400 }, 53 | new BidEntity { Id = 6, HouseId = 3, Bidder = "Bill Mentor", Amount = 201000 }, 54 | new BidEntity { Id = 7, HouseId = 4, Bidder = "Melissa Kirk", Amount = 410000 }, 55 | new BidEntity { Id = 8, HouseId = 4, Bidder = "Scott Max", Amount = 450000 }, 56 | new BidEntity { Id = 9, HouseId = 4, Bidder = "Christine James", Amount = 470000 }, 57 | new BidEntity { Id = 10, HouseId = 5, Bidder = "Omesh Carim", Amount = 450000 }, 58 | new BidEntity { Id = 11, HouseId = 5, Bidder = "Charlotte Max", Amount = 150000 }, 59 | new BidEntity { Id = 12, HouseId = 5, Bidder = "Marcus Scott", Amount = 170000 }, 60 | }); 61 | } 62 | } -------------------------------------------------------------------------------- /Api/Dtos/BidDto.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | public record BidDto(int Id, int HouseId, 4 | [property: Required]string Bidder, int Amount); -------------------------------------------------------------------------------- /Api/Dtos/HouseDetailDto.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | public record HouseDetailDto(int Id, [property: Required]string? Address, [property: Required]string? Country, 4 | string? Description, int Price, string? Photo); -------------------------------------------------------------------------------- /Api/Dtos/HouseDto.cs: -------------------------------------------------------------------------------- 1 | public record HouseDto(int Id, string? Address, string? Country, int Price); -------------------------------------------------------------------------------- /Api/Migrations/20220311103107_Initial.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Migrations; 5 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 6 | 7 | #nullable disable 8 | 9 | namespace Api.Migrations 10 | { 11 | [DbContext(typeof(HouseDbContext))] 12 | [Migration("20220311103107_Initial")] 13 | partial class Initial 14 | { 15 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 16 | { 17 | #pragma warning disable 612, 618 18 | modelBuilder.HasAnnotation("ProductVersion", "6.0.3"); 19 | 20 | modelBuilder.Entity("BidEntity", b => 21 | { 22 | b.Property("Id") 23 | .ValueGeneratedOnAdd() 24 | .HasColumnType("INTEGER"); 25 | 26 | b.Property("Amount") 27 | .HasColumnType("INTEGER"); 28 | 29 | b.Property("Bidder") 30 | .IsRequired() 31 | .HasColumnType("TEXT"); 32 | 33 | b.Property("HouseId") 34 | .HasColumnType("INTEGER"); 35 | 36 | b.HasKey("Id"); 37 | 38 | b.ToTable("Bids"); 39 | 40 | b.HasData( 41 | new 42 | { 43 | Id = 1, 44 | Amount = 200000, 45 | Bidder = "Sonia Reading", 46 | HouseId = 1 47 | }, 48 | new 49 | { 50 | Id = 2, 51 | Amount = 202400, 52 | Bidder = "Dick Johnson", 53 | HouseId = 1 54 | }, 55 | new 56 | { 57 | Id = 3, 58 | Amount = 302400, 59 | Bidder = "Mohammed Vahls", 60 | HouseId = 2 61 | }, 62 | new 63 | { 64 | Id = 4, 65 | Amount = 310500, 66 | Bidder = "Jane Williams", 67 | HouseId = 2 68 | }, 69 | new 70 | { 71 | Id = 5, 72 | Amount = 315400, 73 | Bidder = "John Kepler", 74 | HouseId = 2 75 | }, 76 | new 77 | { 78 | Id = 6, 79 | Amount = 201000, 80 | Bidder = "Bill Mentor", 81 | HouseId = 3 82 | }, 83 | new 84 | { 85 | Id = 7, 86 | Amount = 410000, 87 | Bidder = "Melissa Kirk", 88 | HouseId = 4 89 | }, 90 | new 91 | { 92 | Id = 8, 93 | Amount = 450000, 94 | Bidder = "Scott Max", 95 | HouseId = 4 96 | }, 97 | new 98 | { 99 | Id = 9, 100 | Amount = 470000, 101 | Bidder = "Christine James", 102 | HouseId = 4 103 | }, 104 | new 105 | { 106 | Id = 10, 107 | Amount = 450000, 108 | Bidder = "Omesh Carim", 109 | HouseId = 5 110 | }, 111 | new 112 | { 113 | Id = 11, 114 | Amount = 150000, 115 | Bidder = "Charlotte Max", 116 | HouseId = 5 117 | }, 118 | new 119 | { 120 | Id = 12, 121 | Amount = 170000, 122 | Bidder = "Marcus Scott", 123 | HouseId = 5 124 | }); 125 | }); 126 | 127 | modelBuilder.Entity("HouseEntity", b => 128 | { 129 | b.Property("Id") 130 | .ValueGeneratedOnAdd() 131 | .HasColumnType("INTEGER"); 132 | 133 | b.Property("Address") 134 | .HasColumnType("TEXT"); 135 | 136 | b.Property("Country") 137 | .HasColumnType("TEXT"); 138 | 139 | b.Property("Description") 140 | .HasColumnType("TEXT"); 141 | 142 | b.Property("Price") 143 | .HasColumnType("INTEGER"); 144 | 145 | b.HasKey("Id"); 146 | 147 | b.ToTable("Houses"); 148 | 149 | b.HasData( 150 | new 151 | { 152 | Id = 1, 153 | Address = "12 Valley of Kings, Geneva", 154 | Country = "Switzerland", 155 | Description = "A superb detached Victorian property on one of the town's finest roads, within easy reach of Barnes Village. The property has in excess of 6000 sq/ft of accommodation, a driveway and landscaped garden.", 156 | Price = 900000 157 | }, 158 | new 159 | { 160 | Id = 2, 161 | Address = "89 Road of Forks, Bern", 162 | Country = "Switzerland", 163 | Description = "This impressive family home, which dates back to approximately 1840, offers original period features throughout and is set back from the road with off street parking for up to six cars and an original Coach House, which has been incorporated into the main house to provide further accommodation. ", 164 | Price = 500000 165 | }, 166 | new 167 | { 168 | Id = 3, 169 | Address = "Grote Hof 12, Amsterdam", 170 | Country = "The Netherlands", 171 | Description = "This house has been designed and built to an impeccable standard offering luxurious and elegant living. The accommodation is arranged over four floors comprising a large entrance hall, living room with tall sash windows, dining room, study and WC on the ground floor.", 172 | Price = 200500 173 | }, 174 | new 175 | { 176 | Id = 4, 177 | Address = "Meel Kade 321, The Hague", 178 | Country = "The Netherlands", 179 | Description = "Discreetly situated a unique two storey period home enviably located on the corner of Krom Road and Recht Road offering seclusion and privacy. The house features a magnificent double height reception room with doors leading directly out onto a charming courtyard garden.", 180 | Price = 259500 181 | }, 182 | new 183 | { 184 | Id = 5, 185 | Address = "Oude Gracht 32, Utrecht", 186 | Country = "The Netherlands", 187 | Description = "This luxurious three bedroom flat is contemporary in style and benefits from the use of a gymnasium and a reserved underground parking space.", 188 | Price = 400500 189 | }); 190 | }); 191 | #pragma warning restore 612, 618 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /Api/Migrations/20220311103107_Initial.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Api.Migrations 6 | { 7 | public partial class Initial : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.CreateTable( 12 | name: "Bids", 13 | columns: table => new 14 | { 15 | Id = table.Column(type: "INTEGER", nullable: false) 16 | .Annotation("Sqlite:Autoincrement", true), 17 | HouseId = table.Column(type: "INTEGER", nullable: false), 18 | Bidder = table.Column(type: "TEXT", nullable: false), 19 | Amount = table.Column(type: "INTEGER", nullable: false) 20 | }, 21 | constraints: table => 22 | { 23 | table.PrimaryKey("PK_Bids", x => x.Id); 24 | }); 25 | 26 | migrationBuilder.CreateTable( 27 | name: "Houses", 28 | columns: table => new 29 | { 30 | Id = table.Column(type: "INTEGER", nullable: false) 31 | .Annotation("Sqlite:Autoincrement", true), 32 | Address = table.Column(type: "TEXT", nullable: true), 33 | Country = table.Column(type: "TEXT", nullable: true), 34 | Description = table.Column(type: "TEXT", nullable: true), 35 | Price = table.Column(type: "INTEGER", nullable: false) 36 | }, 37 | constraints: table => 38 | { 39 | table.PrimaryKey("PK_Houses", x => x.Id); 40 | }); 41 | 42 | migrationBuilder.InsertData( 43 | table: "Bids", 44 | columns: new[] { "Id", "Amount", "Bidder", "HouseId" }, 45 | values: new object[] { 1, 200000, "Sonia Reading", 1 }); 46 | 47 | migrationBuilder.InsertData( 48 | table: "Bids", 49 | columns: new[] { "Id", "Amount", "Bidder", "HouseId" }, 50 | values: new object[] { 2, 202400, "Dick Johnson", 1 }); 51 | 52 | migrationBuilder.InsertData( 53 | table: "Bids", 54 | columns: new[] { "Id", "Amount", "Bidder", "HouseId" }, 55 | values: new object[] { 3, 302400, "Mohammed Vahls", 2 }); 56 | 57 | migrationBuilder.InsertData( 58 | table: "Bids", 59 | columns: new[] { "Id", "Amount", "Bidder", "HouseId" }, 60 | values: new object[] { 4, 310500, "Jane Williams", 2 }); 61 | 62 | migrationBuilder.InsertData( 63 | table: "Bids", 64 | columns: new[] { "Id", "Amount", "Bidder", "HouseId" }, 65 | values: new object[] { 5, 315400, "John Kepler", 2 }); 66 | 67 | migrationBuilder.InsertData( 68 | table: "Bids", 69 | columns: new[] { "Id", "Amount", "Bidder", "HouseId" }, 70 | values: new object[] { 6, 201000, "Bill Mentor", 3 }); 71 | 72 | migrationBuilder.InsertData( 73 | table: "Bids", 74 | columns: new[] { "Id", "Amount", "Bidder", "HouseId" }, 75 | values: new object[] { 7, 410000, "Melissa Kirk", 4 }); 76 | 77 | migrationBuilder.InsertData( 78 | table: "Bids", 79 | columns: new[] { "Id", "Amount", "Bidder", "HouseId" }, 80 | values: new object[] { 8, 450000, "Scott Max", 4 }); 81 | 82 | migrationBuilder.InsertData( 83 | table: "Bids", 84 | columns: new[] { "Id", "Amount", "Bidder", "HouseId" }, 85 | values: new object[] { 9, 470000, "Christine James", 4 }); 86 | 87 | migrationBuilder.InsertData( 88 | table: "Bids", 89 | columns: new[] { "Id", "Amount", "Bidder", "HouseId" }, 90 | values: new object[] { 10, 450000, "Omesh Carim", 5 }); 91 | 92 | migrationBuilder.InsertData( 93 | table: "Bids", 94 | columns: new[] { "Id", "Amount", "Bidder", "HouseId" }, 95 | values: new object[] { 11, 150000, "Charlotte Max", 5 }); 96 | 97 | migrationBuilder.InsertData( 98 | table: "Bids", 99 | columns: new[] { "Id", "Amount", "Bidder", "HouseId" }, 100 | values: new object[] { 12, 170000, "Marcus Scott", 5 }); 101 | 102 | migrationBuilder.InsertData( 103 | table: "Houses", 104 | columns: new[] { "Id", "Address", "Country", "Description", "Price" }, 105 | values: new object[] { 1, "12 Valley of Kings, Geneva", "Switzerland", "A superb detached Victorian property on one of the town's finest roads, within easy reach of Barnes Village. The property has in excess of 6000 sq/ft of accommodation, a driveway and landscaped garden.", 900000 }); 106 | 107 | migrationBuilder.InsertData( 108 | table: "Houses", 109 | columns: new[] { "Id", "Address", "Country", "Description", "Price" }, 110 | values: new object[] { 2, "89 Road of Forks, Bern", "Switzerland", "This impressive family home, which dates back to approximately 1840, offers original period features throughout and is set back from the road with off street parking for up to six cars and an original Coach House, which has been incorporated into the main house to provide further accommodation. ", 500000 }); 111 | 112 | migrationBuilder.InsertData( 113 | table: "Houses", 114 | columns: new[] { "Id", "Address", "Country", "Description", "Price" }, 115 | values: new object[] { 3, "Grote Hof 12, Amsterdam", "The Netherlands", "This house has been designed and built to an impeccable standard offering luxurious and elegant living. The accommodation is arranged over four floors comprising a large entrance hall, living room with tall sash windows, dining room, study and WC on the ground floor.", 200500 }); 116 | 117 | migrationBuilder.InsertData( 118 | table: "Houses", 119 | columns: new[] { "Id", "Address", "Country", "Description", "Price" }, 120 | values: new object[] { 4, "Meel Kade 321, The Hague", "The Netherlands", "Discreetly situated a unique two storey period home enviably located on the corner of Krom Road and Recht Road offering seclusion and privacy. The house features a magnificent double height reception room with doors leading directly out onto a charming courtyard garden.", 259500 }); 121 | 122 | migrationBuilder.InsertData( 123 | table: "Houses", 124 | columns: new[] { "Id", "Address", "Country", "Description", "Price" }, 125 | values: new object[] { 5, "Oude Gracht 32, Utrecht", "The Netherlands", "This luxurious three bedroom flat is contemporary in style and benefits from the use of a gymnasium and a reserved underground parking space.", 400500 }); 126 | } 127 | 128 | protected override void Down(MigrationBuilder migrationBuilder) 129 | { 130 | migrationBuilder.DropTable( 131 | name: "Bids"); 132 | 133 | migrationBuilder.DropTable( 134 | name: "Houses"); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Api/Migrations/20220314113837_photo.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Migrations; 5 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 6 | 7 | #nullable disable 8 | 9 | namespace Api.Migrations 10 | { 11 | [DbContext(typeof(HouseDbContext))] 12 | [Migration("20220314113837_photo")] 13 | partial class photo 14 | { 15 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 16 | { 17 | #pragma warning disable 612, 618 18 | modelBuilder.HasAnnotation("ProductVersion", "6.0.3"); 19 | 20 | modelBuilder.Entity("BidEntity", b => 21 | { 22 | b.Property("Id") 23 | .ValueGeneratedOnAdd() 24 | .HasColumnType("INTEGER"); 25 | 26 | b.Property("Amount") 27 | .HasColumnType("INTEGER"); 28 | 29 | b.Property("Bidder") 30 | .IsRequired() 31 | .HasColumnType("TEXT"); 32 | 33 | b.Property("HouseId") 34 | .HasColumnType("INTEGER"); 35 | 36 | b.HasKey("Id"); 37 | 38 | b.HasIndex("HouseId"); 39 | 40 | b.ToTable("Bids"); 41 | 42 | b.HasData( 43 | new 44 | { 45 | Id = 1, 46 | Amount = 200000, 47 | Bidder = "Sonia Reading", 48 | HouseId = 1 49 | }, 50 | new 51 | { 52 | Id = 2, 53 | Amount = 202400, 54 | Bidder = "Dick Johnson", 55 | HouseId = 1 56 | }, 57 | new 58 | { 59 | Id = 3, 60 | Amount = 302400, 61 | Bidder = "Mohammed Vahls", 62 | HouseId = 2 63 | }, 64 | new 65 | { 66 | Id = 4, 67 | Amount = 310500, 68 | Bidder = "Jane Williams", 69 | HouseId = 2 70 | }, 71 | new 72 | { 73 | Id = 5, 74 | Amount = 315400, 75 | Bidder = "John Kepler", 76 | HouseId = 2 77 | }, 78 | new 79 | { 80 | Id = 6, 81 | Amount = 201000, 82 | Bidder = "Bill Mentor", 83 | HouseId = 3 84 | }, 85 | new 86 | { 87 | Id = 7, 88 | Amount = 410000, 89 | Bidder = "Melissa Kirk", 90 | HouseId = 4 91 | }, 92 | new 93 | { 94 | Id = 8, 95 | Amount = 450000, 96 | Bidder = "Scott Max", 97 | HouseId = 4 98 | }, 99 | new 100 | { 101 | Id = 9, 102 | Amount = 470000, 103 | Bidder = "Christine James", 104 | HouseId = 4 105 | }, 106 | new 107 | { 108 | Id = 10, 109 | Amount = 450000, 110 | Bidder = "Omesh Carim", 111 | HouseId = 5 112 | }, 113 | new 114 | { 115 | Id = 11, 116 | Amount = 150000, 117 | Bidder = "Charlotte Max", 118 | HouseId = 5 119 | }, 120 | new 121 | { 122 | Id = 12, 123 | Amount = 170000, 124 | Bidder = "Marcus Scott", 125 | HouseId = 5 126 | }); 127 | }); 128 | 129 | modelBuilder.Entity("HouseEntity", b => 130 | { 131 | b.Property("Id") 132 | .ValueGeneratedOnAdd() 133 | .HasColumnType("INTEGER"); 134 | 135 | b.Property("Address") 136 | .HasColumnType("TEXT"); 137 | 138 | b.Property("Country") 139 | .HasColumnType("TEXT"); 140 | 141 | b.Property("Description") 142 | .HasColumnType("TEXT"); 143 | 144 | b.Property("Photo") 145 | .HasColumnType("TEXT"); 146 | 147 | b.Property("Price") 148 | .HasColumnType("INTEGER"); 149 | 150 | b.HasKey("Id"); 151 | 152 | b.ToTable("Houses"); 153 | 154 | b.HasData( 155 | new 156 | { 157 | Id = 1, 158 | Address = "12 Valley of Kings, Geneva", 159 | Country = "Switzerland", 160 | Description = "A superb detached Victorian property on one of the town's finest roads, within easy reach of Barnes Village. The property has in excess of 6000 sq/ft of accommodation, a driveway and landscaped garden.", 161 | Price = 900000 162 | }, 163 | new 164 | { 165 | Id = 2, 166 | Address = "89 Road of Forks, Bern", 167 | Country = "Switzerland", 168 | Description = "This impressive family home, which dates back to approximately 1840, offers original period features throughout and is set back from the road with off street parking for up to six cars and an original Coach House, which has been incorporated into the main house to provide further accommodation. ", 169 | Price = 500000 170 | }, 171 | new 172 | { 173 | Id = 3, 174 | Address = "Grote Hof 12, Amsterdam", 175 | Country = "The Netherlands", 176 | Description = "This house has been designed and built to an impeccable standard offering luxurious and elegant living. The accommodation is arranged over four floors comprising a large entrance hall, living room with tall sash windows, dining room, study and WC on the ground floor.", 177 | Price = 200500 178 | }, 179 | new 180 | { 181 | Id = 4, 182 | Address = "Meel Kade 321, The Hague", 183 | Country = "The Netherlands", 184 | Description = "Discreetly situated a unique two storey period home enviably located on the corner of Krom Road and Recht Road offering seclusion and privacy. The house features a magnificent double height reception room with doors leading directly out onto a charming courtyard garden.", 185 | Price = 259500 186 | }, 187 | new 188 | { 189 | Id = 5, 190 | Address = "Oude Gracht 32, Utrecht", 191 | Country = "The Netherlands", 192 | Description = "This luxurious three bedroom flat is contemporary in style and benefits from the use of a gymnasium and a reserved underground parking space.", 193 | Price = 400500 194 | }); 195 | }); 196 | 197 | modelBuilder.Entity("BidEntity", b => 198 | { 199 | b.HasOne("HouseEntity", "House") 200 | .WithMany("Bids") 201 | .HasForeignKey("HouseId") 202 | .OnDelete(DeleteBehavior.Cascade) 203 | .IsRequired(); 204 | 205 | b.Navigation("House"); 206 | }); 207 | 208 | modelBuilder.Entity("HouseEntity", b => 209 | { 210 | b.Navigation("Bids"); 211 | }); 212 | #pragma warning restore 612, 618 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /Api/Migrations/20220314113837_photo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Api.Migrations 6 | { 7 | public partial class photo : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.AddColumn( 12 | name: "Photo", 13 | table: "Houses", 14 | type: "TEXT", 15 | nullable: true); 16 | 17 | migrationBuilder.CreateIndex( 18 | name: "IX_Bids_HouseId", 19 | table: "Bids", 20 | column: "HouseId"); 21 | 22 | migrationBuilder.AddForeignKey( 23 | name: "FK_Bids_Houses_HouseId", 24 | table: "Bids", 25 | column: "HouseId", 26 | principalTable: "Houses", 27 | principalColumn: "Id", 28 | onDelete: ReferentialAction.Cascade); 29 | } 30 | 31 | protected override void Down(MigrationBuilder migrationBuilder) 32 | { 33 | migrationBuilder.DropForeignKey( 34 | name: "FK_Bids_Houses_HouseId", 35 | table: "Bids"); 36 | 37 | migrationBuilder.DropIndex( 38 | name: "IX_Bids_HouseId", 39 | table: "Bids"); 40 | 41 | migrationBuilder.DropColumn( 42 | name: "Photo", 43 | table: "Houses"); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Api/Migrations/HouseDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 5 | 6 | #nullable disable 7 | 8 | namespace Api.Migrations 9 | { 10 | [DbContext(typeof(HouseDbContext))] 11 | partial class HouseDbContextModelSnapshot : ModelSnapshot 12 | { 13 | protected override void BuildModel(ModelBuilder modelBuilder) 14 | { 15 | #pragma warning disable 612, 618 16 | modelBuilder.HasAnnotation("ProductVersion", "6.0.3"); 17 | 18 | modelBuilder.Entity("BidEntity", b => 19 | { 20 | b.Property("Id") 21 | .ValueGeneratedOnAdd() 22 | .HasColumnType("INTEGER"); 23 | 24 | b.Property("Amount") 25 | .HasColumnType("INTEGER"); 26 | 27 | b.Property("Bidder") 28 | .IsRequired() 29 | .HasColumnType("TEXT"); 30 | 31 | b.Property("HouseId") 32 | .HasColumnType("INTEGER"); 33 | 34 | b.HasKey("Id"); 35 | 36 | b.HasIndex("HouseId"); 37 | 38 | b.ToTable("Bids", (string)null); 39 | 40 | b.HasData( 41 | new 42 | { 43 | Id = 1, 44 | Amount = 200000, 45 | Bidder = "Sonia Reading", 46 | HouseId = 1 47 | }, 48 | new 49 | { 50 | Id = 2, 51 | Amount = 202400, 52 | Bidder = "Dick Johnson", 53 | HouseId = 1 54 | }, 55 | new 56 | { 57 | Id = 3, 58 | Amount = 302400, 59 | Bidder = "Mohammed Vahls", 60 | HouseId = 2 61 | }, 62 | new 63 | { 64 | Id = 4, 65 | Amount = 310500, 66 | Bidder = "Jane Williams", 67 | HouseId = 2 68 | }, 69 | new 70 | { 71 | Id = 5, 72 | Amount = 315400, 73 | Bidder = "John Kepler", 74 | HouseId = 2 75 | }, 76 | new 77 | { 78 | Id = 6, 79 | Amount = 201000, 80 | Bidder = "Bill Mentor", 81 | HouseId = 3 82 | }, 83 | new 84 | { 85 | Id = 7, 86 | Amount = 410000, 87 | Bidder = "Melissa Kirk", 88 | HouseId = 4 89 | }, 90 | new 91 | { 92 | Id = 8, 93 | Amount = 450000, 94 | Bidder = "Scott Max", 95 | HouseId = 4 96 | }, 97 | new 98 | { 99 | Id = 9, 100 | Amount = 470000, 101 | Bidder = "Christine James", 102 | HouseId = 4 103 | }, 104 | new 105 | { 106 | Id = 10, 107 | Amount = 450000, 108 | Bidder = "Omesh Carim", 109 | HouseId = 5 110 | }, 111 | new 112 | { 113 | Id = 11, 114 | Amount = 150000, 115 | Bidder = "Charlotte Max", 116 | HouseId = 5 117 | }, 118 | new 119 | { 120 | Id = 12, 121 | Amount = 170000, 122 | Bidder = "Marcus Scott", 123 | HouseId = 5 124 | }); 125 | }); 126 | 127 | modelBuilder.Entity("HouseEntity", b => 128 | { 129 | b.Property("Id") 130 | .ValueGeneratedOnAdd() 131 | .HasColumnType("INTEGER"); 132 | 133 | b.Property("Address") 134 | .HasColumnType("TEXT"); 135 | 136 | b.Property("Country") 137 | .HasColumnType("TEXT"); 138 | 139 | b.Property("Description") 140 | .HasColumnType("TEXT"); 141 | 142 | b.Property("Photo") 143 | .HasColumnType("TEXT"); 144 | 145 | b.Property("Price") 146 | .HasColumnType("INTEGER"); 147 | 148 | b.HasKey("Id"); 149 | 150 | b.ToTable("Houses", (string)null); 151 | 152 | b.HasData( 153 | new 154 | { 155 | Id = 1, 156 | Address = "12 Valley of Kings, Geneva", 157 | Country = "Switzerland", 158 | Description = "A superb detached Victorian property on one of the town's finest roads, within easy reach of Barnes Village. The property has in excess of 6000 sq/ft of accommodation, a driveway and landscaped garden.", 159 | Price = 900000 160 | }, 161 | new 162 | { 163 | Id = 2, 164 | Address = "89 Road of Forks, Bern", 165 | Country = "Switzerland", 166 | Description = "This impressive family home, which dates back to approximately 1840, offers original period features throughout and is set back from the road with off street parking for up to six cars and an original Coach House, which has been incorporated into the main house to provide further accommodation. ", 167 | Price = 500000 168 | }, 169 | new 170 | { 171 | Id = 3, 172 | Address = "Grote Hof 12, Amsterdam", 173 | Country = "The Netherlands", 174 | Description = "This house has been designed and built to an impeccable standard offering luxurious and elegant living. The accommodation is arranged over four floors comprising a large entrance hall, living room with tall sash windows, dining room, study and WC on the ground floor.", 175 | Price = 200500 176 | }, 177 | new 178 | { 179 | Id = 4, 180 | Address = "Meel Kade 321, The Hague", 181 | Country = "The Netherlands", 182 | Description = "Discreetly situated a unique two storey period home enviably located on the corner of Krom Road and Recht Road offering seclusion and privacy. The house features a magnificent double height reception room with doors leading directly out onto a charming courtyard garden.", 183 | Price = 259500 184 | }, 185 | new 186 | { 187 | Id = 5, 188 | Address = "Oude Gracht 32, Utrecht", 189 | Country = "The Netherlands", 190 | Description = "This luxurious three bedroom flat is contemporary in style and benefits from the use of a gymnasium and a reserved underground parking space.", 191 | Price = 400500 192 | }); 193 | }); 194 | 195 | modelBuilder.Entity("BidEntity", b => 196 | { 197 | b.HasOne("HouseEntity", "House") 198 | .WithMany("Bids") 199 | .HasForeignKey("HouseId") 200 | .OnDelete(DeleteBehavior.Cascade) 201 | .IsRequired(); 202 | 203 | b.Navigation("House"); 204 | }); 205 | 206 | modelBuilder.Entity("HouseEntity", b => 207 | { 208 | b.Navigation("Bids"); 209 | }); 210 | #pragma warning restore 612, 618 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /Api/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using MiniValidation; 3 | 4 | var builder = WebApplication.CreateBuilder(args); 5 | builder.Services.AddEndpointsApiExplorer(); 6 | builder.Services.AddSwaggerGen(); 7 | builder.Services.AddCors(); 8 | 9 | builder.Services.AddDbContext(options => options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)); 10 | builder.Services.AddScoped(); 11 | builder.Services.AddScoped(); 12 | 13 | var app = builder.Build(); 14 | 15 | app.UseSwagger(); 16 | app.UseSwaggerUI(); 17 | 18 | app.UseCors(p => p.WithOrigins("http://localhost:3000").AllowAnyHeader().AllowAnyMethod().AllowCredentials()); 19 | 20 | app.MapHouseEndpoints(); 21 | app.MapBidEndpoints(); 22 | 23 | app.Run(); 24 | -------------------------------------------------------------------------------- /Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Api": { 4 | "commandName": "Project", 5 | "dotnetRunMessages": true, 6 | "launchBrowser": true, 7 | "applicationUrl": "https://localhost:4000", 8 | "environmentVariables": { 9 | "ASPNETCORE_ENVIRONMENT": "Development" 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Api/WebApplicationBidExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using MiniValidation; 3 | 4 | public static class WebApplicationBidExtensions 5 | { 6 | public static void MapBidEndpoints(this WebApplication app) 7 | { 8 | app.MapGet("/house/{houseId:int}/bids", async (int houseId, 9 | IHouseRepository houseRepo, IBidRepository bidRepo) => 10 | { 11 | if (await houseRepo.Get(houseId) == null) 12 | return Results.Problem($"House with Id {houseId} not found", statusCode: 404); 13 | var bids = await bidRepo.Get(houseId); 14 | return Results.Ok(bids); 15 | }).ProducesProblem(404).Produces(StatusCodes.Status200OK); 16 | 17 | app.MapPost("/house/{houseId:int}/bids", async (int houseId, [FromBody] BidDto dto, IBidRepository repo) => 18 | { 19 | if (dto.HouseId != houseId) 20 | return Results.Problem($"House Id of DTO {dto.HouseId} doesn't match with URL data {houseId}", 21 | statusCode: StatusCodes.Status400BadRequest); 22 | if (!MiniValidator.TryValidate(dto, out var errors)) 23 | return Results.ValidationProblem(errors); 24 | var newBid = await repo.Add(dto); 25 | return Results.Created($"/houses/{newBid.HouseId}/bids", newBid); 26 | }).ProducesValidationProblem().ProducesProblem(400).Produces(StatusCodes.Status201Created); 27 | } 28 | } -------------------------------------------------------------------------------- /Api/WebApplicationHouseExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using MiniValidation; 3 | 4 | public static class WebApplicationHouseExtensions 5 | { 6 | public static void MapHouseEndpoints(this WebApplication app) 7 | { 8 | app.MapGet("/houses", (IHouseRepository repo) => repo.GetAll()) 9 | .Produces(StatusCodes.Status200OK); 10 | 11 | app.MapGet("/house/{houseId:int}", async (int houseId, IHouseRepository repo) => 12 | { 13 | var house = await repo.Get(houseId); 14 | if (house == null) 15 | return Results.Problem($"House with ID {houseId} not found.", statusCode: 404); 16 | return Results.Ok(house); 17 | }).ProducesProblem(404).Produces(StatusCodes.Status200OK); 18 | 19 | app.MapPost("/houses", async ([FromBody] HouseDetailDto dto, IHouseRepository repo) => 20 | { 21 | if (!MiniValidator.TryValidate(dto, out var errors)) 22 | return Results.ValidationProblem(errors); 23 | var newHouse = await repo.Add(dto); 24 | return Results.Created($"/house/{newHouse.Id}", newHouse); 25 | }).ProducesValidationProblem().Produces(StatusCodes.Status201Created); 26 | 27 | app.MapPut("/houses", async ([FromBody] HouseDetailDto dto, IHouseRepository repo) => 28 | { 29 | if (!MiniValidator.TryValidate(dto, out var errors)) 30 | return Results.ValidationProblem(errors); 31 | if (await repo.Get(dto.Id) == null) 32 | return Results.Problem($"House with Id {dto.Id} not found", statusCode: 404); 33 | var updatedHouse = await repo.Update(dto); 34 | return Results.Ok(updatedHouse); 35 | }).ProducesValidationProblem().ProducesProblem(404).Produces(StatusCodes.Status200OK); 36 | 37 | app.MapDelete("/houses/{houseId:int}", async (int houseId, IHouseRepository repo) => 38 | { 39 | if (await repo.Get(houseId) == null) 40 | return Results.Problem($"House with Id {houseId} not found", statusCode: 404); 41 | await repo.Delete(houseId); 42 | return Results.Ok(); 43 | }).ProducesProblem(404).Produces(StatusCodes.Status200OK); 44 | } 45 | } -------------------------------------------------------------------------------- /Api/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /HouseExampleImages/164558.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RolandGuijt/ps-globomantics-webapi-react/ae1816e1143630dbfbf3f9f456c5ff0cb9620ab1/HouseExampleImages/164558.jpeg -------------------------------------------------------------------------------- /HouseExampleImages/259600.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RolandGuijt/ps-globomantics-webapi-react/ae1816e1143630dbfbf3f9f456c5ff0cb9620ab1/HouseExampleImages/259600.jpeg -------------------------------------------------------------------------------- /HouseExampleImages/277667.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RolandGuijt/ps-globomantics-webapi-react/ae1816e1143630dbfbf3f9f456c5ff0cb9620ab1/HouseExampleImages/277667.jpeg -------------------------------------------------------------------------------- /HouseExampleImages/462358.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RolandGuijt/ps-globomantics-webapi-react/ae1816e1143630dbfbf3f9f456c5ff0cb9620ab1/HouseExampleImages/462358.jpeg -------------------------------------------------------------------------------- /HouseExampleImages/534182.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RolandGuijt/ps-globomantics-webapi-react/ae1816e1143630dbfbf3f9f456c5ff0cb9620ab1/HouseExampleImages/534182.jpeg -------------------------------------------------------------------------------- /ReactWeb/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /ReactWeb/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /ReactWeb/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default { 18 | // other rules... 19 | parserOptions: { 20 | ecmaVersion: 'latest', 21 | sourceType: 'module', 22 | project: ['./tsconfig.json', './tsconfig.node.json'], 23 | tsconfigRootDir: __dirname, 24 | }, 25 | } 26 | ``` 27 | 28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 31 | -------------------------------------------------------------------------------- /ReactWeb/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ReactWeb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactweb", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@tanstack/react-query": "^5.21.2", 14 | "axios": "^1.6.7", 15 | "bootstrap": "^5.3.2", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0", 18 | "react-router-dom": "^6.22.0" 19 | }, 20 | "devDependencies": { 21 | "@types/react": "^18.2.43", 22 | "@types/react-dom": "^18.2.17", 23 | "@typescript-eslint/eslint-plugin": "^6.14.0", 24 | "@typescript-eslint/parser": "^6.14.0", 25 | "@vitejs/plugin-react-swc": "^3.5.0", 26 | "eslint": "^8.55.0", 27 | "eslint-plugin-react-hooks": "^4.6.0", 28 | "eslint-plugin-react-refresh": "^0.4.5", 29 | "typescript": "^5.2.2", 30 | "vite": "^5.0.8" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ReactWeb/src/ValidationSummary.tsx: -------------------------------------------------------------------------------- 1 | import { AxiosError } from "axios"; 2 | import Problem from "./types/problem"; 3 | 4 | type Args = { 5 | error: AxiosError; 6 | }; 7 | 8 | const ValidationSummary = ({ error }: Args) => { 9 | if (error.response?.status !== 400) return <>; 10 | const errors = error.response?.data.errors; 11 | return ( 12 | <> 13 |
Please fix the following:
14 | {Object.entries(errors).map(([key, value]) => ( 15 |
    16 |
  • 17 | {key}: {value.join(", ")} 18 |
  • 19 |
20 | ))} 21 | 22 | ); 23 | }; 24 | 25 | export default ValidationSummary; 26 | -------------------------------------------------------------------------------- /ReactWeb/src/apiStatus.tsx: -------------------------------------------------------------------------------- 1 | type Args = { 2 | status: "success" | "error" | "pending"; 3 | }; 4 | 5 | const ApiStatus = ({ status }: Args) => { 6 | switch (status) { 7 | case "error": 8 | return
Error communicating with the data backend
; 9 | case "pending": 10 | return
Loading..
; 11 | default: 12 | throw Error("Unknown API state"); 13 | } 14 | }; 15 | 16 | export default ApiStatus; 17 | -------------------------------------------------------------------------------- /ReactWeb/src/bids/Bids.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import ApiStatus from "../apiStatus"; 3 | import { currencyFormatter } from "../config"; 4 | import { useAddBid, useFetchBids } from "../hooks/BidHooks"; 5 | import { Bid } from "../types/bid"; 6 | import { House } from "../types/house"; 7 | 8 | type Args = { 9 | house: House; 10 | }; 11 | 12 | const Bids = ({ house }: Args) => { 13 | const { data, status, isSuccess } = useFetchBids(house.id); 14 | const addBidMutation = useAddBid(); 15 | 16 | const emptyBid = { 17 | id: 0, 18 | houseId: house.id, 19 | bidder: "", 20 | amount: 0, 21 | }; 22 | const [bid, setBid] = useState(emptyBid); 23 | 24 | if (!isSuccess) return ; 25 | 26 | const onBidSubmitClick = () => { 27 | addBidMutation.mutate(bid); 28 | setBid(emptyBid); 29 | }; 30 | 31 | return ( 32 | <> 33 |
34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | {data && 44 | data.map((b) => ( 45 | 46 | 47 | 48 | 49 | ))} 50 | 51 |
BidderAmount
{b.bidder}{currencyFormatter.format(b.amount)}
52 |
53 |
54 |
55 |
56 | setBid({ ...bid, bidder: e.target.value })} 62 | placeholder="Bidder" 63 | > 64 |
65 |
66 | 72 | setBid({ ...bid, amount: parseInt(e.target.value) }) 73 | } 74 | placeholder="Amount" 75 | > 76 |
77 |
78 | 84 |
85 |
86 | 87 | ); 88 | }; 89 | 90 | export default Bids; 91 | -------------------------------------------------------------------------------- /ReactWeb/src/config.ts: -------------------------------------------------------------------------------- 1 | const config = { 2 | baseApiUrl: "https://localhost:4000", 3 | }; 4 | 5 | const currencyFormatter = Intl.NumberFormat("en-US", { 6 | style: "currency", 7 | currency: "USD", 8 | maximumFractionDigits: 0, 9 | }); 10 | 11 | export default config; 12 | export { currencyFormatter }; 13 | -------------------------------------------------------------------------------- /ReactWeb/src/hooks/BidHooks.ts: -------------------------------------------------------------------------------- 1 | import { Bid } from "./../types/bid"; 2 | import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; 3 | import Config from "../config"; 4 | import axios, { AxiosError, AxiosResponse } from "axios"; 5 | import Problem from "../types/problem"; 6 | 7 | const useFetchBids = (houseId: number) => { 8 | return useQuery>({ 9 | queryKey: ["bids", houseId], 10 | queryFn: () => 11 | axios 12 | .get(`${Config.baseApiUrl}/house/${houseId}/bids`) 13 | .then((resp) => resp.data), 14 | }); 15 | }; 16 | 17 | const useAddBid = () => { 18 | const queryClient = useQueryClient(); 19 | return useMutation, Bid>({ 20 | mutationFn: (b) => 21 | axios.post(`${Config.baseApiUrl}/house/${b.houseId}/bids`, b), 22 | onSuccess: (_, bid) => { 23 | queryClient.invalidateQueries({ 24 | queryKey: ["bids", bid.houseId], 25 | }); 26 | }, 27 | }); 28 | }; 29 | 30 | export { useFetchBids, useAddBid }; 31 | -------------------------------------------------------------------------------- /ReactWeb/src/hooks/HouseHooks.ts: -------------------------------------------------------------------------------- 1 | import { useNavigate } from "react-router-dom"; 2 | import { House } from "./../types/house"; 3 | import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; 4 | import config from "../config"; 5 | import axios, { AxiosError, AxiosResponse } from "axios"; 6 | import Problem from "../types/problem"; 7 | 8 | const useFetchHouses = () => { 9 | return useQuery({ 10 | queryKey: ["houses"], 11 | queryFn: () => 12 | axios.get(`${config.baseApiUrl}/houses`).then((resp) => resp.data), 13 | }); 14 | }; 15 | 16 | const useFetchHouse = (id: number) => { 17 | return useQuery({ 18 | queryKey: ["houses", id], 19 | queryFn: () => 20 | axios.get(`${config.baseApiUrl}/house/${id}`).then((resp) => resp.data), 21 | }); 22 | }; 23 | 24 | const useAddHouse = () => { 25 | const queryClient = useQueryClient(); 26 | const nav = useNavigate(); 27 | return useMutation, House>({ 28 | mutationFn: (h) => axios.post(`${config.baseApiUrl}/houses`, h), 29 | onSuccess: () => { 30 | queryClient.invalidateQueries({ queryKey: ["houses"] }); 31 | nav("/"); 32 | }, 33 | }); 34 | }; 35 | 36 | const useUpdateHouse = () => { 37 | const queryClient = useQueryClient(); 38 | const nav = useNavigate(); 39 | return useMutation, House>({ 40 | mutationFn: (h) => axios.put(`${config.baseApiUrl}/houses`, h), 41 | onSuccess: (_, house) => { 42 | queryClient.invalidateQueries({ queryKey: ["houses"] }); 43 | nav(`/house/${house.id}`); 44 | }, 45 | }); 46 | }; 47 | 48 | const useDeleteHouse = () => { 49 | const queryClient = useQueryClient(); 50 | const nav = useNavigate(); 51 | return useMutation({ 52 | mutationFn: (h) => axios.delete(`${config.baseApiUrl}/houses/${h.id}`), 53 | onSuccess: () => { 54 | queryClient.invalidateQueries({ queryKey: ["houses"] }); 55 | nav("/"); 56 | }, 57 | }); 58 | }; 59 | 60 | export { 61 | useFetchHouses, 62 | useFetchHouse, 63 | useAddHouse, 64 | useUpdateHouse, 65 | useDeleteHouse, 66 | }; 67 | -------------------------------------------------------------------------------- /ReactWeb/src/house/HouseAdd.tsx: -------------------------------------------------------------------------------- 1 | import { useAddHouse } from "../hooks/HouseHooks"; 2 | import { House } from "../types/house"; 3 | import ValidationSummary from "../ValidationSummary"; 4 | import HouseForm from "./HouseForm"; 5 | 6 | const HouseAdd = () => { 7 | const addHouseMutation = useAddHouse(); 8 | 9 | const house: House = { 10 | address: "", 11 | country: "", 12 | description: "", 13 | price: 0, 14 | id: 0, 15 | photo: "", 16 | }; 17 | 18 | return ( 19 | <> 20 | {addHouseMutation.isError && ( 21 | 22 | )} 23 | addHouseMutation.mutate(house)} 26 | /> 27 | 28 | ); 29 | }; 30 | 31 | export default HouseAdd; 32 | -------------------------------------------------------------------------------- /ReactWeb/src/house/HouseDetail.tsx: -------------------------------------------------------------------------------- 1 | import Bids from "../bids/Bids"; 2 | import { Link, useParams } from "react-router-dom"; 3 | import { useDeleteHouse, useFetchHouse } from "../hooks/HouseHooks"; 4 | import ApiStatus from "../apiStatus"; 5 | import { currencyFormatter } from "../config"; 6 | import defaultImage from "./defaultPhoto"; 7 | 8 | const HouseDetail = () => { 9 | const { id } = useParams(); 10 | if (!id) throw Error("House id not found"); 11 | const houseId = parseInt(id); 12 | 13 | const { data, status, isSuccess } = useFetchHouse(houseId); 14 | 15 | const deleteHouseMutation = useDeleteHouse(); 16 | 17 | if (!isSuccess) return ; 18 | 19 | if (!data) return
House not found.
; 20 | 21 | return ( 22 |
23 |
24 |
25 | House pic 30 |
31 |
32 |
33 | 37 | Edit 38 | 39 |
40 |
41 | 50 |
51 |
52 |
53 |
54 |
55 |
{data.country}
56 |
57 |
58 |

{data.address}

59 |
60 |
61 |

62 | {currencyFormatter.format(data.price)} 63 |

64 |
65 |
66 |
{data.description}
67 |
68 | 69 |
70 |
71 | ); 72 | }; 73 | 74 | export default HouseDetail; 75 | -------------------------------------------------------------------------------- /ReactWeb/src/house/HouseEdit.tsx: -------------------------------------------------------------------------------- 1 | import { useParams } from "react-router-dom"; 2 | import ApiStatus from "../apiStatus"; 3 | import { useFetchHouse, useUpdateHouse } from "../hooks/HouseHooks"; 4 | import ValidationSummary from "../ValidationSummary"; 5 | import HouseForm from "./HouseForm"; 6 | 7 | const HouseEdit = () => { 8 | const { id } = useParams(); 9 | if (!id) throw Error("Need a house id"); 10 | const houseId = parseInt(id); 11 | 12 | const { data, status, isSuccess } = useFetchHouse(houseId); 13 | const updateHouseMutation = useUpdateHouse(); 14 | 15 | if (!isSuccess) return ; 16 | 17 | return ( 18 | <> 19 | {updateHouseMutation.isError && ( 20 | 21 | )} 22 | { 25 | updateHouseMutation.mutate(house); 26 | }} 27 | /> 28 | 29 | ); 30 | }; 31 | 32 | export default HouseEdit; 33 | -------------------------------------------------------------------------------- /ReactWeb/src/house/HouseForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import toBase64 from "../toBase64"; 3 | import { House } from "../types/house"; 4 | 5 | type Args = { 6 | house: House; 7 | submitted: (house: House) => void; 8 | }; 9 | 10 | const HouseForm = ({ house, submitted }: Args) => { 11 | const [houseState, setHouseState] = useState({ ...house }); 12 | 13 | const onSubmit: React.MouseEventHandler = async (e) => { 14 | e.preventDefault(); 15 | submitted(houseState); 16 | }; 17 | 18 | const onFileSelected = async ( 19 | e: React.ChangeEvent 20 | ): Promise => { 21 | e.preventDefault(); 22 | e.target.files && 23 | e.target.files[0] && 24 | setHouseState({ 25 | ...houseState, 26 | photo: await toBase64(e.target.files[0]), 27 | }); 28 | }; 29 | 30 | return ( 31 |
32 |
33 | 34 | 40 | setHouseState({ ...houseState, address: e.target.value }) 41 | } 42 | /> 43 |
44 |
45 | 46 | 52 | setHouseState({ ...houseState, country: e.target.value }) 53 | } 54 | /> 55 |
56 |
57 | 58 |