mfoud444 commited on
Commit
8edbc20
·
1 Parent(s): fe0c6b5

first commit

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +25 -0
  2. .gitignore +7 -0
  3. Backend.csproj +26 -0
  4. Backend.sln +25 -0
  5. Dockerfile +24 -0
  6. Properties/launchSettings.json +41 -0
  7. appsettings.Development.json +8 -0
  8. d.cmd +3 -0
  9. d.sh +3 -0
  10. docker-compose.debug.yml +18 -0
  11. docker-compose.yml +12 -0
  12. p.md +250 -0
  13. run.cmd +2 -0
  14. screenshots/Artify ERD.png +0 -0
  15. src/Controllers/ArtworksController.cs +129 -0
  16. src/Controllers/BookingsController.cs +156 -0
  17. src/Controllers/CategoriesController.cs +90 -0
  18. src/Controllers/OrdersController.cs +126 -0
  19. src/Controllers/PaymentsController.cs +60 -0
  20. src/Controllers/UsersController.cs +133 -0
  21. src/Controllers/WorkshopsController.cs +86 -0
  22. src/DTO/ArtworkDTO.cs +79 -0
  23. src/DTO/BookingDTO.cs +24 -0
  24. src/DTO/CategoryDTO.cs +33 -0
  25. src/DTO/OrderDTO.cs +53 -0
  26. src/DTO/OrderDetailDTO.cs +30 -0
  27. src/DTO/PaymentDTO.cs +47 -0
  28. src/DTO/UserDTO.cs +98 -0
  29. src/DTO/WorkshopDTO.cs +102 -0
  30. src/Database/DatabaseContext.cs +50 -0
  31. src/Entities/Artwork.cs +42 -0
  32. src/Entities/Booking.cs +32 -0
  33. src/Entities/Category.cs +16 -0
  34. src/Entities/Order.cs +28 -0
  35. src/Entities/OrderDetails.cs +20 -0
  36. src/Entities/Payment.cs +24 -0
  37. src/Entities/User.cs +54 -0
  38. src/Entities/Workshop.cs +50 -0
  39. src/Middleware/ErrorHandlerMiddleware.cs +21 -0
  40. src/Program.cs +152 -0
  41. src/Repository/ArtworkRepository.cs +101 -0
  42. src/Repository/BookingRepository.cs +121 -0
  43. src/Repository/CategoryRepository.cs +68 -0
  44. src/Repository/OrderRepository.cs +105 -0
  45. src/Repository/PaymentRepository.cs +59 -0
  46. src/Repository/UserRepository.cs +96 -0
  47. src/Repository/WorkshopRepository.cs +87 -0
  48. src/Services/artwork/ArtworkService.cs +122 -0
  49. src/Services/artwork/IArtworkService.cs +19 -0
  50. src/Services/booking/BookingService.cs +253 -0
.dockerignore ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ **/.classpath
2
+ **/.dockerignore
3
+ **/.env
4
+ **/.git
5
+ **/.gitignore
6
+ **/.project
7
+ **/.settings
8
+ **/.toolstarget
9
+ **/.vs
10
+ **/.vscode
11
+ **/*.*proj.user
12
+ **/*.dbmdl
13
+ **/*.jfm
14
+ **/bin
15
+ **/charts
16
+ **/docker-compose*
17
+ **/compose*
18
+ **/Dockerfile*
19
+ **/node_modules
20
+ **/npm-debug.log
21
+ **/obj
22
+ **/secrets.dev.yaml
23
+ **/values.dev.yaml
24
+ LICENSE
25
+ README.md
.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ .vscode
2
+ .DS_Store
3
+ bin
4
+ obj
5
+ *.http
6
+ appsettings.json
7
+ Migrations
Backend.csproj ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <Project Sdk="Microsoft.NET.Sdk.Web">
2
+
3
+ <PropertyGroup>
4
+ <TargetFramework>net8.0</TargetFramework>
5
+ <Nullable>enable</Nullable>
6
+ <ImplicitUsings>enable</ImplicitUsings>
7
+ <InvariantGlobalization>true</InvariantGlobalization>
8
+ </PropertyGroup>
9
+
10
+ <ItemGroup>
11
+ <PackageReference Include="AutoMapper" Version="13.0.1" />
12
+ <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.8" />
13
+ <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
14
+ <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
15
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
16
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
17
+ <PrivateAssets>all</PrivateAssets>
18
+ </PackageReference>
19
+ <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.1.0" />
20
+ <PackageReference Include="Npgsql" Version="8.0.4" />
21
+ <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
22
+ <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
23
+ <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.1.0" />
24
+ </ItemGroup>
25
+
26
+ </Project>
Backend.sln ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 
2
+ Microsoft Visual Studio Solution File, Format Version 12.00
3
+ # Visual Studio Version 17
4
+ VisualStudioVersion = 17.5.002.0
5
+ MinimumVisualStudioVersion = 10.0.40219.1
6
+ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend", "Backend.csproj", "{098C6FA2-0AD6-4DB6-A189-6F70D6E3362B}"
7
+ EndProject
8
+ Global
9
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
10
+ Debug|Any CPU = Debug|Any CPU
11
+ Release|Any CPU = Release|Any CPU
12
+ EndGlobalSection
13
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
14
+ {098C6FA2-0AD6-4DB6-A189-6F70D6E3362B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15
+ {098C6FA2-0AD6-4DB6-A189-6F70D6E3362B}.Debug|Any CPU.Build.0 = Debug|Any CPU
16
+ {098C6FA2-0AD6-4DB6-A189-6F70D6E3362B}.Release|Any CPU.ActiveCfg = Release|Any CPU
17
+ {098C6FA2-0AD6-4DB6-A189-6F70D6E3362B}.Release|Any CPU.Build.0 = Release|Any CPU
18
+ EndGlobalSection
19
+ GlobalSection(SolutionProperties) = preSolution
20
+ HideSolutionNode = FALSE
21
+ EndGlobalSection
22
+ GlobalSection(ExtensibilityGlobals) = postSolution
23
+ SolutionGuid = {A69E6D17-A6C9-4FBB-97A3-7ED53714E982}
24
+ EndGlobalSection
25
+ EndGlobal
Dockerfile ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
2
+ WORKDIR /app
3
+ EXPOSE 5125
4
+
5
+ ENV ASPNETCORE_URLS=http://0.0.0.0:5125
6
+ # ENV ConnectionStrings__Local="Host=aws-0-eu-central-1.pooler.supabase.com;Port=6543;Database=postgres;Username=postgres.xergfpyauvdbavdyzzbk;Password=009988Ppooii@@@@"
7
+ ENV ConnectionStrings__Local="Host=aws-0-eu-central-1.pooler.supabase.com;Port=6543;Database=postgres;Username=postgres.hpbtdersdvvpxrkmhbfe;Password=009988Ppooii@@@@"
8
+ ENV JWT__KEY="your-long-secret-key"
9
+ ENV JWT__ISSUER="your-issuer"
10
+ ENV JWT__AUDIENCE="your-audience"
11
+ FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
12
+ WORKDIR /src
13
+ COPY ["Backend.csproj", "./"]
14
+ RUN dotnet restore "Backend.csproj"
15
+ COPY . .
16
+ RUN dotnet build "Backend.csproj" -c Release -o /app/build
17
+
18
+ FROM build AS publish
19
+ RUN dotnet publish "Backend.csproj" -c Release -o /app/publish
20
+
21
+ FROM base AS final
22
+ WORKDIR /app
23
+ COPY --from=publish /app/publish .
24
+ ENTRYPOINT ["dotnet", "Backend.dll"]
Properties/launchSettings.json ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "http://json.schemastore.org/launchsettings.json",
3
+ "iisSettings": {
4
+ "windowsAuthentication": false,
5
+ "anonymousAuthentication": true,
6
+ "iisExpress": {
7
+ "applicationUrl": "http://localhost:58899",
8
+ "sslPort": 44376
9
+ }
10
+ },
11
+ "profiles": {
12
+ "http": {
13
+ "commandName": "Project",
14
+ "dotnetRunMessages": true,
15
+ "launchBrowser": true,
16
+ "launchUrl": "swagger",
17
+ "applicationUrl": "http://localhost:5125",
18
+ "environmentVariables": {
19
+ "ASPNETCORE_ENVIRONMENT": "Development"
20
+ }
21
+ },
22
+ "https": {
23
+ "commandName": "Project",
24
+ "dotnetRunMessages": true,
25
+ "launchBrowser": true,
26
+ "launchUrl": "swagger",
27
+ "applicationUrl": "https://localhost:7097;http://localhost:5125",
28
+ "environmentVariables": {
29
+ "ASPNETCORE_ENVIRONMENT": "Development"
30
+ }
31
+ },
32
+ "IIS Express": {
33
+ "commandName": "IISExpress",
34
+ "launchBrowser": true,
35
+ "launchUrl": "swagger",
36
+ "environmentVariables": {
37
+ "ASPNETCORE_ENVIRONMENT": "Development"
38
+ }
39
+ }
40
+ }
41
+ }
appsettings.Development.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "Logging": {
3
+ "LogLevel": {
4
+ "Default": "Information",
5
+ "Microsoft.AspNetCore": "Warning"
6
+ }
7
+ }
8
+ }
d.cmd ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ git add .
2
+ git commit -m "first commit"
3
+ git push -u origin main
d.sh ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ git add .
2
+ git commit -m "first commit"
3
+ git push -u origin main
docker-compose.debug.yml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Please refer https://aka.ms/HTTPSinContainer on how to setup an https developer certificate for your ASP.NET Core service.
2
+
3
+ version: '3.4'
4
+
5
+ services:
6
+ backend:
7
+ image: backend
8
+ build:
9
+ context: .
10
+ dockerfile: ./Dockerfile
11
+ args:
12
+ - configuration=Debug
13
+ ports:
14
+ - 5125:5125
15
+ environment:
16
+ - ASPNETCORE_ENVIRONMENT=Development
17
+ volumes:
18
+ - ~/.vsdbg:c:\remote_debugger:rw
docker-compose.yml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Please refer https://aka.ms/HTTPSinContainer on how to setup an https developer certificate for your ASP.NET Core service.
2
+
3
+ version: '3.4'
4
+
5
+ services:
6
+ backend:
7
+ image: backend
8
+ build:
9
+ context: .
10
+ dockerfile: ./Dockerfile
11
+ ports:
12
+ - 5125:5125
p.md ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Unhandled exception. System.ArgumentException: Host can't be null
2
+ at Npgsql.NpgsqlConnectionStringBuilder.PostProcessAndValidate()
3
+ at Npgsql.NpgsqlSlimDataSourceBuilder.PrepareConfiguration()
4
+ at Npgsql.NpgsqlSlimDataSourceBuilder.Build()
5
+ at Npgsql.NpgsqlDataSourceBuilder.Build()
6
+ at Program.<>c__DisplayClass0_0.<<Main>$>b__0(DbContextOptionsBuilder options) in /src/src/Program.cs:line 32
7
+ at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.<>c__DisplayClass1_0`2.<AddDbContext>b__0(IServiceProvider _, DbContextOptionsBuilder b)
8
+ at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.CreateDbContextOptions[TContext](IServiceProvider applicationServiceProvider, Action`2 optionsAction)
9
+ at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.<>c__DisplayClass17_0`1.<AddCoreServices>b__0(IServiceProvider p)
10
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
11
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
12
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
13
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
14
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
15
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
16
+ at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
17
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
18
+ at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
19
+ at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
20
+ at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.<>c__17`1.<AddCoreServices>b__17_1(IServiceProvider p)
21
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
22
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
23
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
24
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
25
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
26
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
27
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
28
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
29
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
30
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
31
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
32
+ at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
33
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
34
+ at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
35
+ at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
36
+ at Program.<Main>$(String[] args) in /src/src/Program.cs:line 111
37
+ ==> Exited with status 139
38
+ ==> Common ways to troubleshoot your deploy: https://docs.render.com/troubleshooting-deploys
39
+ Unhandled exception. System.ArgumentException: Host can't be null
40
+ at Npgsql.NpgsqlConnectionStringBuilder.PostProcessAndValidate()
41
+ at Npgsql.NpgsqlSlimDataSourceBuilder.PrepareConfiguration()
42
+ at Npgsql.NpgsqlSlimDataSourceBuilder.Build()
43
+ at Npgsql.NpgsqlDataSourceBuilder.Build()
44
+ at Program.<>c__DisplayClass0_0.<<Main>$>b__0(DbContextOptionsBuilder options) in /src/src/Program.cs:line 32
45
+ at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.<>c__DisplayClass1_0`2.<AddDbContext>b__0(IServiceProvider _, DbContextOptionsBuilder b)
46
+ at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.CreateDbContextOptions[TContext](IServiceProvider applicationServiceProvider, Action`2 optionsAction)
47
+ at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.<>c__DisplayClass17_0`1.<AddCoreServices>b__0(IServiceProvider p)
48
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
49
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
50
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
51
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
52
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
53
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
54
+ at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
55
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
56
+ at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
57
+ at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
58
+ at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.<>c__17`1.<AddCoreServices>b__17_1(IServiceProvider p)
59
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
60
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
61
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
62
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
63
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
64
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
65
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
66
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
67
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
68
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
69
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
70
+ at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
71
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
72
+ at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
73
+ at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
74
+ at Program.<Main>$(String[] args) in /src/src/Program.cs:line 111
75
+
76
+
77
+
78
+ FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
79
+ WORKDIR /app
80
+ EXPOSE 5125
81
+
82
+ ENV ASPNETCORE_URLS=http://0.0.0.0:5125
83
+
84
+ FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
85
+ ARG configuration=Release
86
+ WORKDIR /src
87
+ COPY ["Backend.csproj", "./"]
88
+ RUN dotnet restore "Backend.csproj"
89
+ COPY . .
90
+ WORKDIR "/src/."
91
+ RUN dotnet build "Backend.csproj" -c $configuration -o /app/build
92
+
93
+ FROM build AS publish
94
+ ARG configuration=Release
95
+
96
+ RUN dotnet publish "Backend.csproj" -c $configuration -o /app/publish /p:UseAppHost=false
97
+
98
+ FROM base AS final
99
+ WORKDIR /app
100
+ COPY --from=publish /app/publish .
101
+ ENTRYPOINT ["dotnet", "Backend.dll"]
102
+
103
+
104
+ using System.Text;
105
+ using System.Text.Json.Serialization;
106
+ using Backend_Teamwork.src.Database;
107
+ using Backend_Teamwork.src.Entities;
108
+ using Backend_Teamwork.src.Middleware;
109
+ using Backend_Teamwork.src.Repository;
110
+ using Backend_Teamwork.src.Services.artwork;
111
+ using Backend_Teamwork.src.Services.booking;
112
+ using Backend_Teamwork.src.Services.category;
113
+ using Backend_Teamwork.src.Services.order;
114
+ using Backend_Teamwork.src.Services.user;
115
+ using Backend_Teamwork.src.Services.workshop;
116
+ using Backend_Teamwork.src.Utils;
117
+ using Microsoft.AspNetCore.Authentication.JwtBearer;
118
+ using Microsoft.EntityFrameworkCore;
119
+ using Microsoft.IdentityModel.Tokens;
120
+ using Npgsql;
121
+ using static Backend_Teamwork.src.Entities.User;
122
+
123
+ var builder = WebApplication.CreateBuilder(args);
124
+
125
+ //connect to database
126
+ var dataSourceBuilder = new NpgsqlDataSourceBuilder(
127
+ builder.Configuration.GetConnectionString("Local")
128
+ );
129
+ dataSourceBuilder.MapEnum<UserRole>();
130
+ dataSourceBuilder.MapEnum<Status>();
131
+
132
+ //add database connection
133
+ builder.Services.AddDbContext<DatabaseContext>(options =>
134
+ {
135
+ options.UseNpgsql(dataSourceBuilder.Build());
136
+ });
137
+
138
+ //add auto-mapper
139
+ builder.Services.AddAutoMapper(typeof(MapperProfile).Assembly);
140
+
141
+ //add DI services
142
+ builder.Services.AddScoped<ICategoryService, CategoryService>().AddScoped<CategoryRepository>();
143
+ builder.Services.AddScoped<IArtworkService, ArtworkService>().AddScoped<ArtworkRepository>();
144
+ builder.Services.AddScoped<IUserService, UserService>().AddScoped<UserRepository>();
145
+ builder.Services.AddScoped<IOrderService, OrderService>().AddScoped<OrderRepository>();
146
+ builder.Services.AddScoped<IWorkshopService, WorkshopService>().AddScoped<WorkshopRepository>();
147
+ builder.Services.AddScoped<IBookingService, BookingService>().AddScoped<BookingRepository>();
148
+
149
+ //builder.Services.AddScoped<IPaymentService, IPaymentService>().AddScoped<PaymentRepository>();
150
+
151
+
152
+ //add logic for authentication
153
+ builder
154
+ .Services.AddAuthentication(options =>
155
+ {
156
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
157
+ options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
158
+ })
159
+ .AddJwtBearer(Options =>
160
+ {
161
+ Options.TokenValidationParameters = new TokenValidationParameters
162
+ {
163
+ ValidateIssuer = true,
164
+ ValidateAudience = true,
165
+ ValidateLifetime = true,
166
+ ValidateIssuerSigningKey = true,
167
+ ValidIssuer = builder.Configuration["Jwt:Issuer"],
168
+ ValidAudience = builder.Configuration["Jwt:Audience"],
169
+ IssuerSigningKey = new SymmetricSecurityKey(
170
+ Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])
171
+ ),
172
+ };
173
+ });
174
+
175
+ //add logic for athorization
176
+ builder.Services.AddAuthorization(options =>
177
+ {
178
+ options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
179
+ options.AddPolicy("CustomerOnly", policy => policy.RequireRole("Customer"));
180
+ });
181
+
182
+ // *** add CORS settings ***
183
+ builder.Services.AddCors(options =>
184
+ {
185
+ options.AddPolicy("AllowSpecificOrigin",
186
+ builder => builder.WithOrigins("http://localhost:5173")
187
+ .AllowAnyHeader()
188
+ .AllowAnyMethod());
189
+ });
190
+
191
+
192
+ //add controllers
193
+ builder.Services.AddControllers();
194
+ builder.Services.AddEndpointsApiExplorer();
195
+ builder
196
+ .Services.AddControllers()
197
+ .AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
198
+
199
+ //add swagger
200
+ builder.Services.AddSwaggerGen();
201
+
202
+ var app = builder.Build();
203
+ app.UseRouting();
204
+ app.MapGet("/", () => "Server is running");
205
+
206
+ //Convert to Timestamp format
207
+ AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
208
+
209
+ //
210
+
211
+ //test database connection
212
+ using (var scope = app.Services.CreateScope())
213
+ {
214
+ var dbContext = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
215
+ try
216
+ {
217
+ if (dbContext.Database.CanConnect())
218
+ {
219
+ Console.WriteLine("Database is connected");
220
+ }
221
+ else
222
+ {
223
+ Console.WriteLine("Unable to connect to the database.");
224
+ }
225
+ }
226
+ catch (Exception ex)
227
+ {
228
+ Console.WriteLine($"Database connection failed: {ex.Message}");
229
+ }
230
+ }
231
+ app.UseHttpsRedirection();
232
+
233
+ //use middleware
234
+ app.UseMiddleware<ErrorHandlerMiddleware>();
235
+ app.UseAuthentication();
236
+ app.UseAuthorization();
237
+
238
+ //use controllers
239
+ app.MapControllers();
240
+
241
+ //use swagger
242
+ if (app.Environment.IsDevelopment())
243
+ {
244
+ app.UseSwagger();
245
+ app.UseSwaggerUI();
246
+ }
247
+ app.Run();
248
+
249
+
250
+
run.cmd ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ cd frontend
2
+ npm run dev
screenshots/Artify ERD.png ADDED
src/Controllers/ArtworksController.cs ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.Security.Claims;
2
+ using Backend_Teamwork.src.DTO;
3
+ using Backend_Teamwork.src.Entities;
4
+ using Backend_Teamwork.src.Services.artwork;
5
+ using Backend_Teamwork.src.Utils;
6
+ using Microsoft.AspNetCore.Authorization;
7
+ using Microsoft.AspNetCore.Mvc;
8
+ using static Backend_Teamwork.src.DTO.ArtworkDTO;
9
+
10
+ namespace Backend_Teamwork.src.Controllers
11
+ {
12
+ [ApiController]
13
+ [Route("api/v1/[controller]")]
14
+ public class ArtworksController : ControllerBase
15
+ {
16
+ private readonly IArtworkService _artworkService;
17
+
18
+ // Constructor
19
+ public ArtworksController(IArtworkService service)
20
+ {
21
+ _artworkService = service;
22
+ }
23
+
24
+ // Create
25
+ // End-Point: api/v1/artworks
26
+ [HttpPost]
27
+ [Authorize(Roles = "Artist")]
28
+ public async Task<ActionResult<ArtworkReadDto>> CreateOne(
29
+ [FromBody] ArtworkCreateDto createDto
30
+ )
31
+ {
32
+ // extract user information
33
+ var authenticateClaims = HttpContext.User;
34
+ // get user id from claim
35
+ var userId = authenticateClaims
36
+ .FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!
37
+ .Value;
38
+ // string => guid
39
+ var userGuid = new Guid(userId);
40
+
41
+ var createdArtwork = await _artworkService.CreateOneAsync(userGuid, createDto);
42
+ return Ok(createdArtwork);
43
+ }
44
+
45
+ // Get all artworks
46
+ // End-Point: api/v1/artworks
47
+ [HttpGet]
48
+ public async Task<ActionResult<PaginatedResponse<List<ArtworkReadDto>>>> GetAll(
49
+ [FromQuery] PaginationOptions paginationOptions
50
+ )
51
+ {
52
+ var artworkList = await _artworkService.GetAllAsync(paginationOptions);
53
+ var totalCount = await _artworkService.GetArtworkCountAsync(); // Get the total count
54
+ var response = new PaginatedResponse<List<ArtworkReadDto>>
55
+ {
56
+ Items = artworkList,
57
+ TotalCount = totalCount
58
+ };
59
+ return Ok(response);
60
+ }
61
+
62
+ // Get by artwork id
63
+ // End-Point: api/v1/artworks/{id}
64
+ [HttpGet("{id}")]
65
+ public async Task<ActionResult<ArtworkReadDto>> GetById([FromRoute] Guid id)
66
+ {
67
+ var artwork = await _artworkService.GetByIdAsync(id);
68
+ return Ok(artwork);
69
+ }
70
+
71
+ // Get by artist Id
72
+ // End-Point: api/v1/artworks/artist/{id}
73
+ [HttpGet("artist/{artistId}")]
74
+ public async Task<ActionResult<List<ArtworkReadDto>>> GetByArtistId(
75
+ [FromRoute] Guid artistId
76
+ )
77
+ {
78
+ var artwork = await _artworkService.GetByArtistIdAsync(artistId);
79
+ return Ok(artwork);
80
+ }
81
+
82
+ // Get count of all artworks
83
+ // End-Point: api/v1/artworks/count
84
+ [HttpGet("count")]
85
+ public async Task<ActionResult<int>> GetArtworkCount()
86
+ {
87
+ var count = await _artworkService.GetArtworkCountAsync();
88
+ return Ok(count);
89
+ }
90
+
91
+ // // Get count of artworks by artistId
92
+ // // End-Point: api/v1/artworks/artist/{artistId}/count
93
+ // [HttpGet("artist/{artistId}/count")]
94
+ // public async Task<ActionResult<int>> GetArtworkCountByArtist([FromRoute] Guid artistId)
95
+ // {
96
+ // var count = await _artworkService.GetArtworkCountByArtistAsync(artistId);
97
+ // return Ok(count);
98
+ // }
99
+
100
+ // Update
101
+ // End-Point: api/v1/artworks/{id}
102
+ [HttpPut("{id}")]
103
+ [Authorize(Roles = "Admin,Artist")]
104
+ public async Task<ActionResult> UpdateOne(
105
+ [FromRoute] Guid id,
106
+ [FromBody] ArtworkUpdateDTO updateDTO
107
+ )
108
+ {
109
+ var artwork = await _artworkService.UpdateOneAsync(id, updateDTO);
110
+ return Ok(artwork);
111
+ }
112
+
113
+ // Delete
114
+ // End-Point: api/v1/artworks/{id}
115
+ [HttpDelete("{id}")]
116
+ [Authorize(Roles = "Admin,Artist")]
117
+ public async Task<ActionResult> DeleteOne([FromRoute] Guid id)
118
+ {
119
+ await _artworkService.DeleteOneAsync(id);
120
+ return NoContent();
121
+ }
122
+ }
123
+
124
+ public class PaginatedResponse<T>
125
+ {
126
+ public T Items { get; set; }
127
+ public int TotalCount { get; set; }
128
+ }
129
+ }
src/Controllers/BookingsController.cs ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.Linq;
4
+ using System.Security.Claims;
5
+ using System.Text.Json;
6
+ using System.Threading.Tasks;
7
+ using Backend_Teamwork.src.Entities;
8
+ using Backend_Teamwork.src.Services.booking;
9
+ using Backend_Teamwork.src.Utils;
10
+ using Microsoft.AspNetCore.Authorization;
11
+ using Microsoft.AspNetCore.Mvc;
12
+ using static Backend_Teamwork.src.DTO.BookingDTO;
13
+
14
+ namespace Backend_Teamwork.src.Controllers
15
+ {
16
+ [ApiController]
17
+ [Route("/api/v1/[controller]")]
18
+ public class BookingsController : ControllerBase
19
+ {
20
+ private readonly IBookingService _bookingService;
21
+
22
+ public BookingsController(IBookingService bookingService)
23
+ {
24
+ _bookingService = bookingService;
25
+ }
26
+
27
+ [HttpGet]
28
+ [Authorize(Roles = "Admin")]
29
+ public async Task<ActionResult<List<BookingReadDto>>> GetBookings()
30
+ {
31
+ var bookings = await _bookingService.GetAllAsync();
32
+ return Ok(bookings);
33
+ }
34
+
35
+ [HttpGet("{id}")]
36
+ [Authorize(Roles = "Admin,Customer")]
37
+ public async Task<ActionResult<BookingReadDto>> GetBookingById([FromRoute] Guid id)
38
+ {
39
+ var authClaims = HttpContext.User;
40
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
41
+ var userRole = authClaims.FindFirst(c => c.Type == ClaimTypes.Role)!.Value;
42
+ var convertedUserId = new Guid(userId);
43
+ var booking = await _bookingService.GetByIdAsync(id, convertedUserId, userRole);
44
+ return Ok(booking);
45
+ }
46
+
47
+ [HttpGet("search/{userId:guid}")]
48
+ [Authorize(Roles = "Admin")]
49
+ public async Task<ActionResult<List<BookingReadDto>>> GetBookingsByUserId(
50
+ [FromRoute] Guid userId
51
+ )
52
+ {
53
+ var bookings = await _bookingService.GetByUserIdAsync(userId);
54
+ return Ok(bookings);
55
+ }
56
+
57
+ [HttpGet("my-bookings")]
58
+ [Authorize(Roles = "Customer")]
59
+ public async Task<ActionResult<List<BookingReadDto>>> GetBookingsByUserId()
60
+ {
61
+ var authClaims = HttpContext.User;
62
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
63
+ var convertedUserId = new Guid(userId);
64
+ var bookings = await _bookingService.GetByUserIdAsync(convertedUserId);
65
+ return Ok(bookings);
66
+ }
67
+
68
+ [HttpGet("search/{status:alpha}")]
69
+ [Authorize(Roles = "Admin")]
70
+ public async Task<ActionResult<List<BookingReadDto>>> GetBookingsByStatus(
71
+ [FromRoute] string status
72
+ )
73
+ {
74
+ var bookings = await _bookingService.GetByStatusAsync(status);
75
+ return Ok(bookings);
76
+ }
77
+
78
+ [HttpGet("my-bookings/search/{status}")]
79
+ [Authorize(Roles = "Customer")]
80
+ public async Task<ActionResult<List<BookingReadDto>>> GetBookingsByUserIdAndStatus(
81
+ [FromRoute] string status
82
+ )
83
+ {
84
+ var authClaims = HttpContext.User;
85
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
86
+ var convertedUserId = new Guid(userId);
87
+ var bookings = await _bookingService.GetByUserIdAndStatusAsync(convertedUserId, status);
88
+ return Ok(bookings);
89
+ }
90
+
91
+ [HttpGet("page")]
92
+ [Authorize(Roles = "Admin")]
93
+ public async Task<ActionResult<List<BookingReadDto>>> GetBookingsWithPagination(
94
+ [FromQuery] PaginationOptions paginationOptions
95
+ )
96
+ {
97
+ var bookings = await _bookingService.GetWithPaginationAsync(paginationOptions);
98
+ return Ok(bookings);
99
+ }
100
+
101
+ [HttpGet("my-bookings/page")]
102
+ [Authorize(Roles = "Customer")]
103
+ public async Task<ActionResult<List<BookingReadDto>>> GetBookingsByUserIdWithPagination(
104
+ [FromQuery] PaginationOptions paginationOptions
105
+ )
106
+ {
107
+ var authClaims = HttpContext.User;
108
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
109
+ paginationOptions.Search=userId;
110
+ var bookings = await _bookingService.GetByUserIdWithPaginationAsync(paginationOptions);
111
+ return Ok(bookings);
112
+ }
113
+
114
+ [HttpPost]
115
+ [Authorize(Roles = "Customer")]
116
+ public async Task<ActionResult<BookingReadDto>> CreateBooking(
117
+ [FromBody] BookingCreateDto bookingDTO
118
+ )
119
+ {
120
+ var authClaims = HttpContext.User;
121
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
122
+ var convertedUserId = new Guid(userId);
123
+ var booking = await _bookingService.CreateAsync(bookingDTO, convertedUserId);
124
+ return CreatedAtAction(nameof(CreateBooking), new { id = booking.Id }, booking);
125
+ }
126
+
127
+ [HttpPut("confirm/{id}")]
128
+ [Authorize(Roles = "Admin")]
129
+ public async Task<ActionResult<BookingReadDto>> ConfirmBooking([FromRoute] Guid id)
130
+ {
131
+ var booking = await _bookingService.ConfirmAsync(id);
132
+ return Ok(booking);
133
+ }
134
+
135
+ [HttpPut("reject/{workshopId}")]
136
+ [Authorize(Roles = "Admin")]
137
+ public async Task<ActionResult<List<BookingReadDto>>> RejectBooking(
138
+ [FromRoute] Guid workshopId
139
+ )
140
+ {
141
+ var bookings = await _bookingService.RejectAsync(workshopId);
142
+ return Ok(bookings);
143
+ }
144
+
145
+ [HttpPut("my-bookings/cancel/{id}")]
146
+ [Authorize(Roles = "Customer")]
147
+ public async Task<ActionResult<BookingReadDto>> CancelBooking([FromRoute] Guid id)
148
+ {
149
+ var authClaims = HttpContext.User;
150
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
151
+ var convertedUserId = new Guid(userId);
152
+ var booking = await _bookingService.CancelAsync(id, convertedUserId);
153
+ return Ok(booking);
154
+ }
155
+ }
156
+ }
src/Controllers/CategoriesController.cs ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.Text.Json;
2
+ using Backend_Teamwork.src.Entities;
3
+ using Backend_Teamwork.src.Services.category;
4
+ using Backend_Teamwork.src.Utils;
5
+ using Microsoft.AspNetCore.Authorization;
6
+ using Microsoft.AspNetCore.Mvc;
7
+ using static Backend_Teamwork.src.DTO.CategoryDTO;
8
+
9
+ namespace Backend_Teamwork.src.Controllers
10
+ {
11
+ [ApiController]
12
+ [Route("/api/v1/[controller]")]
13
+ public class CategoriesController : ControllerBase
14
+ {
15
+ private readonly ICategoryService _categoryService;
16
+
17
+ public CategoriesController(ICategoryService categoryService)
18
+ {
19
+ _categoryService = categoryService;
20
+ }
21
+
22
+ [HttpGet]
23
+ public async Task<ActionResult<List<CategoryReadDto>>> GetCategories()
24
+ {
25
+ var categories = await _categoryService.GetAllAsync();
26
+ return Ok(categories);
27
+ }
28
+
29
+ [HttpGet("{id}")]
30
+ public async Task<ActionResult<CategoryReadDto>> GetCategoryById([FromRoute] Guid id)
31
+ {
32
+ var category = await _categoryService.GetByIdAsync(id);
33
+ return Ok(category);
34
+ }
35
+
36
+ [HttpGet("search/{name}")]
37
+ public async Task<ActionResult<CategoryReadDto>> GetCategoryByName([FromRoute] string name)
38
+ {
39
+ var category = await _categoryService.GetByNameAsync(name);
40
+ return Ok(category);
41
+ }
42
+
43
+ [HttpGet("page")]
44
+ public async Task<ActionResult<List<CategoryReadDto>>> GetCategoriesWithPaginationAsync(
45
+ [FromQuery] PaginationOptions paginationOptions
46
+ )
47
+ {
48
+ var categories = await _categoryService.GetWithPaginationAsync(paginationOptions);
49
+ return Ok(categories);
50
+ }
51
+
52
+ [HttpGet("sort")]
53
+ public async Task<ActionResult<List<CategoryReadDto>>> SortCategoriesByName()
54
+ {
55
+ var categories = await _categoryService.SortByNameAsync();
56
+ return Ok(categories);
57
+ }
58
+
59
+ [HttpPost]
60
+ [Authorize(Roles = "Admin")]
61
+ public async Task<ActionResult<CategoryReadDto>> CreateCategory(
62
+ [FromBody] CategoryCreateDto categoryDTO
63
+ )
64
+ {
65
+ var category = await _categoryService.CreateAsync(categoryDTO);
66
+ return CreatedAtAction(nameof(CreateCategory), new { id = category.Id }, category);
67
+ }
68
+
69
+ [HttpPut("{id}")]
70
+ [Authorize(Roles = "Admin")]
71
+ public async Task<ActionResult<CategoryReadDto>> UpdateCategory(
72
+ [FromRoute] Guid id,
73
+ [FromBody] CategoryUpdateDto categoryDTO
74
+ )
75
+ {
76
+ var category = await _categoryService.UpdateAsync(id, categoryDTO);
77
+ return Ok(category);
78
+ }
79
+
80
+ [HttpDelete("{id}")]
81
+ [Authorize(Roles = "Admin")]
82
+ public async Task<ActionResult> DeleteCategory([FromRoute] Guid id)
83
+ {
84
+ await _categoryService.DeleteAsync(id);
85
+ return NoContent();
86
+ }
87
+
88
+
89
+ }
90
+ }
src/Controllers/OrdersController.cs ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.Security.Claims;
2
+ using Backend_Teamwork.src.Entities;
3
+ using Backend_Teamwork.src.Services.order;
4
+ using Backend_Teamwork.src.Utils;
5
+ using Microsoft.AspNetCore.Authorization;
6
+ using Microsoft.AspNetCore.Mvc;
7
+ using static Backend_Teamwork.src.DTO.OrderDTO;
8
+
9
+ namespace Backend_Teamwork.src.Controllers
10
+ {
11
+ [Route("api/v1/[controller]")]
12
+ [ApiController]
13
+ public class OrdersController : ControllerBase
14
+ {
15
+ private readonly IOrderService _orderService;
16
+
17
+ public OrdersController(IOrderService service)
18
+ {
19
+ _orderService = service;
20
+ }
21
+
22
+ // GET: api/v1/orders
23
+ [HttpGet]
24
+ [Authorize(Roles = "Admin")] // Accessible by Admin
25
+ public async Task<ActionResult<List<OrderReadDto>>> GetOrders()
26
+ {
27
+ var orders = await _orderService.GetAllAsync();
28
+ return Ok(orders);
29
+ }
30
+
31
+ // GET: api/v1/orders
32
+ [HttpGet("my-orders")]
33
+ [Authorize(Roles = "Customer")]
34
+ public async Task<ActionResult<List<OrderReadDto>>> GetMyOrders()
35
+ {
36
+ var authClaims = HttpContext.User;
37
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
38
+ var convertedUserId = new Guid(userId);
39
+ var orders = await _orderService.GetAllAsync(convertedUserId);
40
+ return Ok(orders);
41
+ }
42
+
43
+ // GET: api/v1/orders/{id}
44
+ [HttpGet("{id}")]
45
+ [Authorize(Roles = "Admin")] // Accessible by Admin
46
+ public async Task<ActionResult<OrderReadDto>> GetOrderById([FromRoute] Guid id)
47
+ {
48
+ var order = await _orderService.GetByIdAsync(id);
49
+ return Ok(order);
50
+ }
51
+
52
+ // GET: api/v1/orders/{id}
53
+ [HttpGet("my-orders/{id}")]
54
+ [Authorize(Roles = "Customer")]
55
+ public async Task<ActionResult<OrderReadDto>> GetMyOrderById([FromRoute] Guid id)
56
+ {
57
+ var authClaims = HttpContext.User;
58
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
59
+ var convertedUserId = new Guid(userId);
60
+ var order = await _orderService.GetByIdAsync(id, convertedUserId);
61
+ return Ok(order);
62
+ }
63
+
64
+ // POST: api/v1/orders
65
+ [HttpPost("add")]
66
+ [Authorize(Roles = "Customer")]
67
+ public async Task<ActionResult<OrderReadDto>> AddOrder([FromBody] OrderCreateDto createDto)
68
+ {
69
+ // extract user information
70
+ var authenticateClaims = HttpContext.User;
71
+ var userId = authenticateClaims
72
+ .FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!
73
+ .Value;
74
+ var userGuid = new Guid(userId);
75
+
76
+ var orderCreated = await _orderService.CreateOneAsync(userGuid, createDto);
77
+
78
+ return CreatedAtAction(
79
+ nameof(GetOrderById),
80
+ new { id = orderCreated.Id },
81
+ orderCreated
82
+ );
83
+ }
84
+
85
+ // PUT: api/v1/orders/{id}
86
+ [HttpPut("{id}")]
87
+ [Authorize(Roles = "Admin")] // Accessible by Admin
88
+ public async Task<ActionResult<bool>> UpdateOrder(
89
+ [FromRoute] Guid id,
90
+ [FromBody] OrderUpdateDto updateDto
91
+ )
92
+ {
93
+ var isUpdated = await _orderService.UpdateOneAsync(id, updateDto);
94
+ return Ok(isUpdated);
95
+ }
96
+
97
+ // DELETE: api/v1/orders/{id}
98
+ [HttpDelete("{id}")]
99
+ [Authorize(Roles = "Admin")] // Accessible by Admin
100
+ public async Task<ActionResult<bool>> DeleteOrder(Guid id)
101
+ {
102
+ await _orderService.DeleteOneAsync(id);
103
+ return NoContent();
104
+ }
105
+
106
+ // Extra Features
107
+ // GET: api/v1/users/page
108
+ [HttpGet("pagination")]
109
+ [Authorize(Roles = "Admin")] // Accessible by Admin
110
+ public async Task<ActionResult<List<OrderReadDto>>> GetOrdersByPage(
111
+ [FromQuery] PaginationOptions paginationOptions
112
+ )
113
+ {
114
+ var orders = await _orderService.GetOrdersByPage(paginationOptions);
115
+ return Ok(orders);
116
+ }
117
+
118
+ [HttpGet("sort-by-date")]
119
+ [Authorize(Roles = "Admin")]
120
+ public async Task<ActionResult<List<OrderReadDto>>> SortOrdersByDate()
121
+ {
122
+ var orders = await _orderService.SortOrdersByDate();
123
+ return Ok(orders);
124
+ }
125
+ }
126
+ }
src/Controllers/PaymentsController.cs ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Backend_Teamwork.src.Entities;
2
+ using Backend_Teamwork.src.Services.payment;
3
+ using Microsoft.AspNetCore.Authorization;
4
+ using Microsoft.AspNetCore.Mvc;
5
+ using static Backend_Teamwork.src.DTO.PaymentDTO;
6
+
7
+ namespace Backend_Teamwork.src.Controllers
8
+ {
9
+ [ApiController]
10
+ [Route("api/v1/[controller]")]
11
+ public class PaymentController : ControllerBase
12
+ {
13
+ private readonly IPaymentService _paymentService;
14
+
15
+ public PaymentController(IPaymentService paymentService)
16
+ {
17
+ _paymentService = paymentService;
18
+ }
19
+
20
+ [HttpGet]
21
+ [Authorize(Roles = "Admin")]
22
+ public async Task<ActionResult<List<Payment>>> GetPayments()
23
+ {
24
+ var payments = await _paymentService.GetAllAsync();
25
+ return Ok(payments);
26
+ }
27
+
28
+ [HttpGet("{id}")]
29
+ [Authorize(Roles = "Admin")]
30
+ public async Task<ActionResult<Payment>> GetPaymentById(Guid id)
31
+ {
32
+ var payment = await _paymentService.GetByIdAsync(id);
33
+ return Ok(payment);
34
+ }
35
+
36
+ [HttpPost]
37
+ [Authorize]
38
+ public async Task<ActionResult<Payment>> CreatePayment(PaymentCreateDTO newPayment)
39
+ {
40
+ var payment = await _paymentService.CreateOneAsync(newPayment);
41
+ return CreatedAtAction(nameof(GetPaymentById), new { id = payment.Id }, payment);
42
+ }
43
+
44
+ [HttpDelete("{id}")]
45
+ [Authorize(Roles = "Admin")]
46
+ public async Task<ActionResult> DeletePayment(Guid id)
47
+ {
48
+ await _paymentService.DeleteOneAsync(id);
49
+ return NoContent();
50
+ }
51
+
52
+ [HttpPut("{id}")]
53
+ [Authorize(Roles = "Admin")]
54
+ public ActionResult UpdatePayment(Guid id, PaymentUpdateDTO updatedPayment)
55
+ {
56
+ var payment = _paymentService.UpdateOneAsync(id,updatedPayment);
57
+ return Ok(payment);
58
+ }
59
+ }
60
+ }
src/Controllers/UsersController.cs ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.Security.Claims;
2
+ using Backend_Teamwork.src.Services.user;
3
+ using Backend_Teamwork.src.Utils;
4
+ using Microsoft.AspNetCore.Authorization;
5
+ using Microsoft.AspNetCore.Mvc;
6
+ using static Backend_Teamwork.src.DTO.UserDTO;
7
+ using static Backend_Teamwork.src.Entities.User;
8
+
9
+ namespace Backend_Teamwork.src.Controllers
10
+ {
11
+ [ApiController]
12
+ [Route("api/v1/[controller]")]
13
+ public class UsersController : ControllerBase
14
+ {
15
+ private readonly IUserService _userService;
16
+
17
+ // DI
18
+ public UsersController(IUserService service)
19
+ {
20
+ _userService = service;
21
+ }
22
+
23
+ // GET: api/v1/users
24
+ [HttpGet]
25
+ // [Authorize(Roles = "Admin")] // Only Admin
26
+ public async Task<ActionResult<List<UserReadDto>>> GetUsers(
27
+ [FromQuery] PaginationOptions paginationOptions
28
+ )
29
+ {
30
+ var users = await _userService.GetAllAsync(paginationOptions);
31
+ return Ok(users);
32
+ }
33
+
34
+ [HttpGet("{id:guid}")]
35
+ // [Authorize(Roles = "Admin")] // Only Admin
36
+ public async Task<ActionResult<UserReadDto>> GetUserById([FromRoute] Guid id)
37
+ {
38
+ var user = await _userService.GetByIdAsync(id);
39
+ return Ok(user);
40
+ }
41
+
42
+ [HttpGet("profile")]
43
+ // [Authorize]
44
+ public async Task<ActionResult<UserReadDto>> GetInformationById()
45
+ {
46
+ // Get the user ID from the token claims
47
+ var authClaims = HttpContext.User;
48
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
49
+ var convertedUserId = new Guid(userId);
50
+ var user = await _userService.GetByIdAsync(convertedUserId);
51
+ return Ok(user);
52
+ }
53
+
54
+ // GET: api/v1/users/email
55
+ [HttpGet("email/{email}")]
56
+ // [Authorize(Roles = "Admin")] // Only Admin
57
+ public async Task<ActionResult<UserReadDto>> GetByEmail([FromRoute] string email)
58
+ {
59
+ var user = await _userService.GetByEmailAsync(email);
60
+ return Ok(user);
61
+ }
62
+
63
+ // POST: api/v1/users
64
+ [HttpPost]
65
+ public async Task<ActionResult<UserReadDto>> SignUp([FromBody] UserCreateDto createDto)
66
+ {
67
+ var UserCreated = await _userService.CreateOneAsync(createDto);
68
+ return CreatedAtAction(nameof(GetUserById), new { id = UserCreated.Id }, UserCreated);
69
+ }
70
+
71
+ // POST: api/v1/users/create-admin
72
+ [HttpPost("create-admin")]
73
+ // [Authorize(Roles = "Admin")] // Only Admin
74
+ public async Task<ActionResult<UserReadDto>> CreateAdmin([FromBody] UserCreateDto createDto)
75
+ {
76
+ createDto.Role = UserRole.Admin; // Set role as 'Admin'
77
+ var adminCreated = await _userService.CreateOneAsync(createDto);
78
+ return CreatedAtAction(nameof(GetUserById), new { id = adminCreated.Id }, adminCreated);
79
+ }
80
+
81
+ // POST: api/v1/users/signin
82
+ [HttpPost("signin")]
83
+ public async Task<ActionResult<string>> SignIn([FromBody] UserSigninDto signinDto)
84
+ {
85
+ var token = await _userService.SignInAsync(signinDto);
86
+ return Ok(token);
87
+ }
88
+
89
+ [HttpPut("{id:guid}")]
90
+ // [Authorize(Roles = "Admin")] // Only Admin
91
+ public async Task<ActionResult<bool>> UpdateUser(
92
+ [FromRoute] Guid id,
93
+ [FromBody] UserUpdateDto updateDto
94
+ )
95
+ {
96
+ await _userService.UpdateOneAsync(id, updateDto);
97
+ return NoContent();
98
+ } // should ask my teammates
99
+
100
+ [HttpPut("profile")]
101
+ // [Authorize]
102
+ public async Task<ActionResult<bool>> UpdateProfileInformation(
103
+ [FromBody] UserUpdateDto updateDto
104
+ )
105
+ {
106
+ // Get the user ID from the token claims
107
+ var authClaims = HttpContext.User;
108
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
109
+ var convertedUserId = new Guid(userId);
110
+ await _userService.UpdateOneAsync(convertedUserId, updateDto);
111
+ return NoContent();
112
+ }
113
+
114
+ // DELETE: api/v1/users/{id}
115
+ [HttpDelete("{id:guid}")]
116
+ // [Authorize(Roles = "Admin")] // Only Admin
117
+ public async Task<ActionResult<bool>> DeleteUser([FromRoute] Guid id)
118
+ {
119
+ await _userService.DeleteOneAsync(id);
120
+ return NoContent();
121
+ }
122
+
123
+ // Extra Features
124
+ // GET: api/v1/users/count
125
+ [HttpGet("count")]
126
+ // [Authorize(Roles = "Admin")] // Only Admin
127
+ public async Task<ActionResult<int>> GetTotalUsersCount()
128
+ {
129
+ var count = await _userService.GetTotalUsersCountAsync();
130
+ return Ok(count);
131
+ }
132
+ }
133
+ }
src/Controllers/WorkshopsController.cs ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.Security.Claims;
2
+ using Backend_Teamwork.src.Services.workshop;
3
+ using Backend_Teamwork.src.Utils;
4
+ using Microsoft.AspNetCore.Authorization;
5
+ using Microsoft.AspNetCore.Mvc;
6
+ using static Backend_Teamwork.src.DTO.WorkshopDTO;
7
+
8
+ namespace sda_3_online_Backend_Teamwork.src.Controllers
9
+ {
10
+ [ApiController]
11
+ [Route("api/v1/[controller]")]
12
+ public class WorkshopsController : ControllerBase
13
+ {
14
+ private readonly IWorkshopService _workshopService;
15
+
16
+ public WorkshopsController(IWorkshopService service)
17
+ {
18
+ _workshopService = service;
19
+ }
20
+
21
+ [HttpGet]
22
+ public async Task<ActionResult<List<WorkshopReadDTO>>> GetWorkshop()
23
+ {
24
+ var workshops = await _workshopService.GetAllAsync();
25
+ return Ok(workshops);
26
+ }
27
+
28
+ [HttpGet("{id}")]
29
+ public async Task<ActionResult<WorkshopReadDTO>> GetWorkshopById([FromRoute] Guid id)
30
+ {
31
+ var workshop = await _workshopService.GetByIdAsync(id);
32
+ return Ok(workshop);
33
+ }
34
+
35
+ [HttpGet("page")]
36
+ public async Task<ActionResult<WorkshopReadDTO>> GetWorkShopByPage(
37
+ [FromQuery] PaginationOptions paginationOptions
38
+ )
39
+ {
40
+ var workshops = await _workshopService.GetAllAsync(paginationOptions);
41
+ return Ok(workshops);
42
+ }
43
+
44
+ [HttpPost]
45
+ [Authorize(Roles = "Admin,Artist")]
46
+ public async Task<ActionResult<WorkshopReadDTO>> CreateWorkshop(
47
+ [FromBody] WorkshopCreateDTO createDto
48
+ )
49
+ {
50
+ // extract user information
51
+ var authenticateClaims = HttpContext.User;
52
+ // get user id from claim
53
+ var userId = authenticateClaims
54
+ .FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!
55
+ .Value;
56
+ // string => guid
57
+ var userGuid = new Guid(userId);
58
+
59
+ var workshopCreated = await _workshopService.CreateOneAsync(userGuid, createDto);
60
+ return CreatedAtAction(
61
+ nameof(GetWorkshopById),
62
+ new { id = workshopCreated.Id },
63
+ workshopCreated
64
+ );
65
+ }
66
+
67
+ [HttpDelete("{id}")]
68
+ [Authorize(Roles = "Admin,Artist")]
69
+ public async Task<ActionResult<bool>> DeleteWorkshop([FromRoute] Guid id)
70
+ {
71
+ var isDeleted = await _workshopService.DeleteOneAsync(id);
72
+ return NoContent();
73
+ }
74
+
75
+ [HttpPut("{id}")]
76
+ [Authorize(Roles = "Admin,Artist")]
77
+ public async Task<ActionResult<bool>> UpdateWorkshop(
78
+ Guid id,
79
+ [FromBody] WorkshopUpdateDTO updateDto
80
+ )
81
+ {
82
+ var updateWorkshop = await _workshopService.UpdateOneAsync(id, updateDto);
83
+ return Ok(updateWorkshop);
84
+ }
85
+ }
86
+ }
src/DTO/ArtworkDTO.cs ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.ComponentModel.DataAnnotations;
2
+ using Backend_Teamwork.src.Entities;
3
+ using static Backend_Teamwork.src.DTO.UserDTO;
4
+
5
+ namespace Backend_Teamwork.src.DTO
6
+ {
7
+ public class ArtworkDTO
8
+ {
9
+ // create Artwork
10
+ public class ArtworkCreateDto
11
+ {
12
+ [
13
+ Required(ErrorMessage = "Title shouldn't be null"),
14
+ MinLength(6, ErrorMessage = "Title should be at at least 6 characters"),
15
+ MaxLength(30, ErrorMessage = "Title shouldn't be more than 30 characters")
16
+ ]
17
+ public string Title { get; set; }
18
+
19
+ [
20
+ Required(ErrorMessage = "Description shouldn't be null"),
21
+ MinLength(30, ErrorMessage = "Description should be at at least 30 characters"),
22
+ MaxLength(200, ErrorMessage = "Description shouldn't be more than 200 characters")
23
+ ]
24
+ public string Description { get; set; }
25
+
26
+ [Range(1, int.MaxValue, ErrorMessage = "Quantity should be greater than zero.")]
27
+ public int Quantity { get; set; }
28
+
29
+ [Range(1.0, double.MaxValue, ErrorMessage = "Price should be greater than zero.")]
30
+ public decimal Price { get; set; }
31
+ public DateTime? CreatedAt { get; set; } = DateTime.Now;
32
+
33
+ [Required(ErrorMessage = "Category Id shouldn't be null")]
34
+ public Guid CategoryId { get; set; }
35
+ }
36
+
37
+ // read data (get data)
38
+ public class ArtworkReadDto
39
+ {
40
+ public Guid Id { get; set; }
41
+ public string Title { get; set; }
42
+ public string Description { get; set; }
43
+ public int Quantity { get; set; }
44
+ public decimal Price { get; set; }
45
+ public DateTime CreatedAt { get; set; }
46
+ public Category Category { get; set; }
47
+ public UserReadDto User { get; set; }
48
+ }
49
+
50
+ // update
51
+ public class ArtworkUpdateDTO
52
+ {
53
+ [
54
+ Required(ErrorMessage = "Title shouldn't be null"),
55
+ MinLength(6, ErrorMessage = "Title should be at at least 6 characters"),
56
+ MaxLength(30, ErrorMessage = "Title shouldn't be more than 30 characters")
57
+ ]
58
+ public string Title { get; set; }
59
+
60
+ [
61
+ Required(ErrorMessage = "Description shouldn't be null"),
62
+ MinLength(30, ErrorMessage = "Description should be at at least 30 characters"),
63
+ MaxLength(200, ErrorMessage = "Description shouldn't be more than 200 characters")
64
+ ]
65
+ public string Description { get; set; }
66
+
67
+ [Range(1, int.MaxValue, ErrorMessage = "Quantity should be greater than zero.")]
68
+ public int Quantity { get; set; }
69
+
70
+ [Range(1.0, double.MaxValue, ErrorMessage = "Price should be greater than zero.")]
71
+ public decimal Price { get; set; }
72
+ }
73
+ public class ArtworkResponseDto
74
+ {
75
+ public List<ArtworkReadDto> Artworks { get; set; }
76
+ public int TotalCount { get; set; }
77
+ }
78
+ }
79
+ }
src/DTO/BookingDTO.cs ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.ComponentModel.DataAnnotations;
2
+ using Backend_Teamwork.src.Entities;
3
+
4
+ namespace Backend_Teamwork.src.DTO
5
+ {
6
+ public class BookingDTO
7
+ {
8
+ public class BookingCreateDto
9
+ {
10
+ [Required(ErrorMessage = "Workshop Id shouldn't be null"),]
11
+ public Guid WorkshopId { get; set; }
12
+ public DateTime? CreatedAt { get; set; } = DateTime.Now;
13
+ }
14
+
15
+ public class BookingReadDto
16
+ {
17
+ public Guid Id { get; set; }
18
+ public Status Status { get; set; }
19
+ public DateTime CreatedAt { get; set; }
20
+ public Workshop Workshop { get; set; }
21
+ public User User { get; set; }
22
+ }
23
+ }
24
+ }
src/DTO/CategoryDTO.cs ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.ComponentModel.DataAnnotations;
2
+
3
+ namespace Backend_Teamwork.src.DTO
4
+ {
5
+ public class CategoryDTO
6
+ {
7
+ public class CategoryCreateDto
8
+ {
9
+ [
10
+ Required(ErrorMessage = "Name shouldn't be null"),
11
+ MinLength(2, ErrorMessage = "Name should be at at least 2 characters"),
12
+ MaxLength(10, ErrorMessage = "Name shouldn't be more than 10 characters")
13
+ ]
14
+ public string Name { get; set; }
15
+ }
16
+
17
+ public class CategoryUpdateDto
18
+ {
19
+ [
20
+ Required(ErrorMessage = "Name shouldn't be null"),
21
+ MinLength(2, ErrorMessage = "Name should be at at least 2 characters"),
22
+ MaxLength(10, ErrorMessage = "Name shouldn't be more than 10 characters")
23
+ ]
24
+ public string Name { get; set; }
25
+ }
26
+
27
+ public class CategoryReadDto
28
+ {
29
+ public Guid Id { get; set; }
30
+ public string Name { get; set; }
31
+ }
32
+ }
33
+ }
src/DTO/OrderDTO.cs ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.ComponentModel.DataAnnotations;
2
+ using static Backend_Teamwork.src.DTO.OrderDetailDTO;
3
+ using static Backend_Teamwork.src.DTO.UserDTO;
4
+
5
+ namespace Backend_Teamwork.src.DTO
6
+ {
7
+ public class OrderDTO
8
+ {
9
+ // DTO for creating a new order
10
+ public class OrderCreateDto
11
+ {
12
+ [
13
+ Required(ErrorMessage = "Address shouldn't be null"),
14
+ MinLength(10, ErrorMessage = "Address should be at at least 10 characters"),
15
+ MaxLength(30, ErrorMessage = "Address shouldn't be more than 30 characters")
16
+ ]
17
+ public string ShippingAddress { get; set; }
18
+ public DateTime? CreatedAt { get; set; } = DateTime.Now;
19
+
20
+ [Required(ErrorMessage = "Order details shouldn't be null")]
21
+ public List<OrderDetailCreateDto> OrderDetails { get; set; }
22
+ }
23
+
24
+ // DTO for reading order data
25
+ public class OrderReadDto
26
+ {
27
+ public Guid Id { get; set; }
28
+ public decimal TotalAmount { get; set; }
29
+ public string? ShippingAddress { get; set; }
30
+ public DateTime? CreatedAt { get; set; }
31
+ public UserReadDto User { get; set; }
32
+ public List<OrderDetailReadDto> OrderDetails { get; set; }
33
+ }
34
+
35
+ // DTO for updating an existing order
36
+ public class OrderUpdateDto
37
+ {
38
+ [Range(
39
+ 1.0,
40
+ double.MaxValue,
41
+ ErrorMessage = "Total amount should be greater than zero."
42
+ )]
43
+ public decimal TotalAmount { get; set; }
44
+
45
+ [
46
+ Required(ErrorMessage = "Address shouldn't be null"),
47
+ MinLength(10, ErrorMessage = "Address should be at at least 10 characters"),
48
+ MaxLength(30, ErrorMessage = "Address shouldn't be more than 30 characters")
49
+ ]
50
+ public string? ShippingAddress { get; set; }
51
+ }
52
+ }
53
+ }
src/DTO/OrderDetailDTO.cs ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.ComponentModel.DataAnnotations;
2
+ using static Backend_Teamwork.src.DTO.ArtworkDTO;
3
+
4
+ namespace Backend_Teamwork.src.DTO
5
+ {
6
+ public class OrderDetailDTO
7
+ {
8
+ public class OrderDetailCreateDto
9
+ {
10
+ [Required(ErrorMessage = "Artwork Id shouldn't be null")]
11
+ public Guid ArtworkId { get; set; }
12
+
13
+ [Range(1, int.MaxValue, ErrorMessage = "Quantity should be greater than zero.")]
14
+ public int Quantity { get; set; }
15
+ }
16
+
17
+ public class OrderDetailReadDto
18
+ {
19
+ public Guid Id { get; set; }
20
+ public int Quantity { get; set; }
21
+ public ArtworkReadDto? Artwork { get; set; }
22
+ }
23
+
24
+ public class OrderDetailUpdateDto
25
+ {
26
+ [Range(1, int.MaxValue, ErrorMessage = "Quantity should be greater than zero.")]
27
+ public int Quantity { get; set; }
28
+ }
29
+ }
30
+ }
src/DTO/PaymentDTO.cs ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.ComponentModel.DataAnnotations;
2
+ using Backend_Teamwork.src.Entities;
3
+
4
+ namespace Backend_Teamwork.src.DTO
5
+ {
6
+ public class PaymentDTO
7
+ {
8
+ public class PaymentCreateDTO
9
+ {
10
+ [
11
+ Required(ErrorMessage = "Payment method shouldn't be null"),
12
+ MinLength(10, ErrorMessage = "Payment method should be at at least 10 characters"),
13
+ MaxLength(30, ErrorMessage = "Payment method shouldn't be more than 30 characters")
14
+ ]
15
+ public string PaymentMethod { get; set; }
16
+
17
+ [Range(1.0, double.MaxValue, ErrorMessage = "Price should be greater than zero.")]
18
+ public decimal Amount { get; set; }
19
+ public DateTime? CreatedAt { get; set; } = DateTime.Now;
20
+ public Guid? OrderId { get; set; } = Guid.Empty;
21
+ public Guid? BookingId { get; set; } = Guid.Empty;
22
+ }
23
+
24
+ public class PaymentReadDTO
25
+ {
26
+ public Guid Id { get; set; }
27
+ public string PaymentMethod { get; set; }
28
+ public decimal Amount { get; set; }
29
+ public DateTime CreatedAt { get; set; }
30
+ public Order? Order { get; set; }
31
+ public Booking? Booking { get; set; }
32
+ }
33
+
34
+ public class PaymentUpdateDTO
35
+ {
36
+ [
37
+ Required(ErrorMessage = "Payment method shouldn't be null"),
38
+ MinLength(10, ErrorMessage = "Payment method should be at at least 10 characters"),
39
+ MaxLength(30, ErrorMessage = "Payment method shouldn't be more than 30 characters")
40
+ ]
41
+ public string PaymentMethod { get; set; }
42
+
43
+ [Range(1.0, double.MaxValue, ErrorMessage = "Amount should be greater than zero.")]
44
+ public decimal Amount { get; set; }
45
+ }
46
+ }
47
+ }
src/DTO/UserDTO.cs ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.ComponentModel.DataAnnotations;
2
+ using Backend_Teamwork.src.Utils;
3
+ using static Backend_Teamwork.src.Entities.User;
4
+
5
+ namespace Backend_Teamwork.src.DTO
6
+ {
7
+ public class UserDTO
8
+ {
9
+ // DTO for creating a new User (including Artist)
10
+ public class UserCreateDto
11
+ {
12
+ [
13
+ Required(ErrorMessage = "Name shouldn't be null"),
14
+ MinLength(2, ErrorMessage = "Name should be at at least 2 characters"),
15
+ MaxLength(10, ErrorMessage = "Name shouldn't be more than 10 characters")
16
+ ]
17
+ public string Name { get; set; }
18
+
19
+ [
20
+ Required(ErrorMessage = "Phone number shouldn't be null"),
21
+ RegularExpression(
22
+ @"^\+966[5][0-9]{8}$",
23
+ ErrorMessage = "Phone number should be a valid Saudi phone number"
24
+ )
25
+ ]
26
+ public string PhoneNumber { get; set; }
27
+
28
+ [
29
+ Required(ErrorMessage = "Email shouldn't be null"),
30
+ EmailAddress(ErrorMessage = "Email should be with right format: @gmail.com")
31
+ ]
32
+ public string Email { get; set; }
33
+
34
+ [
35
+ Required(ErrorMessage = "Password shouldn't be null"),
36
+ MinLength(8, ErrorMessage = "Password should be at at least 8 characters")
37
+ ]
38
+ public string Password { get; set; }
39
+
40
+ [Required(ErrorMessage = "Role shouldn't be null")]
41
+ public UserRole Role { get; set; } = UserRole.Customer; // Default to Customer
42
+
43
+ // Artist-specific properties (optional)
44
+ public string? Description { get; set; } // Nullable, only for Artists
45
+ }
46
+
47
+ public class UserSigninDto
48
+ {
49
+ [
50
+ Required(ErrorMessage = "Email shouldn't be null"),
51
+ EmailAddress(ErrorMessage = "Email should be with right format: @gmail.com")
52
+ ]
53
+ public string Email { get; set; }
54
+
55
+ [Required(ErrorMessage = "Password shouldn't be null")]
56
+ public string Password { get; set; }
57
+ }
58
+
59
+ // DTO for reading User data (including Artist)
60
+ public class UserReadDto
61
+ {
62
+ public Guid Id { get; set; }
63
+ public string? Name { get; set; }
64
+ public string? PhoneNumber { get; set; }
65
+ public string? Email { get; set; }
66
+ public UserRole Role { get; set; }
67
+
68
+ // Artist-specific properties (optional)
69
+ public string? Description { get; set; } // Nullable, only for Artists
70
+ }
71
+
72
+ // DTO for updating an existing User (including Artist)
73
+ [AtLeastOneRequired(ErrorMessage = "At least one property must be updated.")]
74
+ public class UserUpdateDto
75
+ {
76
+ [
77
+ MinLength(2, ErrorMessage = "Name should be at at least 2 characters"),
78
+ MaxLength(10, ErrorMessage = "Name shouldn't be more than 10 characters")
79
+ ]
80
+ public string? Name { get; set; }
81
+
82
+ [RegularExpression(
83
+ @"^\+966[5][0-9]{8}$",
84
+ ErrorMessage = "Phone number should be a valid Saudi phone number"
85
+ )]
86
+ public string? PhoneNumber { get; set; }
87
+
88
+ [EmailAddress(ErrorMessage = "Email should be with right format: @gmail.com")]
89
+ public string? Email { get; set; }
90
+
91
+ [MinLength(8, ErrorMessage = "Password should be at at least 8 characters")]
92
+ public string? Password { get; set; }
93
+
94
+ [MinLength(2, ErrorMessage = "Description should be at at least 2 characters")]
95
+ public string? Description { get; set; }
96
+ }
97
+ }
98
+ }
src/DTO/WorkshopDTO.cs ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.ComponentModel.DataAnnotations;
2
+ using Backend_Teamwork.src.Entities;
3
+
4
+ namespace Backend_Teamwork.src.DTO
5
+ {
6
+ public class WorkshopDTO
7
+ {
8
+ public class WorkshopCreateDTO
9
+ {
10
+ [
11
+ Required(ErrorMessage = "Name shouldn't be null"),
12
+ MinLength(10, ErrorMessage = "Name should be at at least 10 characters"),
13
+ MaxLength(30, ErrorMessage = "Name shouldn't be more than 30 characters")
14
+ ]
15
+ public string Name { get; set; }
16
+
17
+ [
18
+ Required(ErrorMessage = "Location shouldn't be null"),
19
+ MinLength(10, ErrorMessage = "Location should be at at least 10 characters"),
20
+ MaxLength(30, ErrorMessage = "Location shouldn't be more than 30 characters")
21
+ ]
22
+ public string Location { get; set; }
23
+
24
+ [
25
+ Required(ErrorMessage = "Description shouldn't be null"),
26
+ MinLength(30, ErrorMessage = "Description should be at at least 30 characters"),
27
+ MaxLength(200, ErrorMessage = "Description shouldn't be more than 200 characters")
28
+ ]
29
+ public string Description { get; set; }
30
+
31
+ [Required(ErrorMessage = "StartTime shouldn't be null")]
32
+ public DateTime StartTime { get; set; }
33
+
34
+ [Required(ErrorMessage = "EndTime shouldn't be null")]
35
+ public DateTime EndTime { get; set; }
36
+
37
+ [Range(1.0, double.MaxValue, ErrorMessage = "Price should be greater than zero.")]
38
+ public decimal Price { get; set; }
39
+
40
+ [Range(1, int.MaxValue, ErrorMessage = "Capacity should be greater than zero.")]
41
+ public int Capacity { get; set; }
42
+
43
+ [Required(ErrorMessage = "Availability shouldn't be null")]
44
+ public bool Availability { get; set; }
45
+ public DateTime? CreatedAt { get; set; } = DateTime.Now;
46
+ }
47
+
48
+ public class WorkshopReadDTO
49
+ {
50
+ public Guid Id { get; set; }
51
+ public string? Name { get; set; }
52
+ public string? Location { get; set; }
53
+ public string? Description { get; set; }
54
+ public DateTime StartTime { get; set; }
55
+ public DateTime EndTime { get; set; }
56
+ public decimal Price { get; set; }
57
+ public int Capacity { get; set; }
58
+ public bool Availability { get; set; }
59
+ public DateTime CreatedAt { get; set; }
60
+ public User User { get; set; }
61
+ }
62
+
63
+ public class WorkshopUpdateDTO
64
+ {
65
+ [
66
+ Required(ErrorMessage = "Name shouldn't be null"),
67
+ MinLength(10, ErrorMessage = "Name should be at at least 10 characters"),
68
+ MaxLength(30, ErrorMessage = "Name shouldn't be more than 30 characters")
69
+ ]
70
+ public string Name { get; set; }
71
+
72
+ [
73
+ Required(ErrorMessage = "Location shouldn't be null"),
74
+ MinLength(10, ErrorMessage = "Location should be at at least 10 characters"),
75
+ MaxLength(30, ErrorMessage = "Location shouldn't be more than 30 characters")
76
+ ]
77
+ public string Location { get; set; }
78
+
79
+ [
80
+ Required(ErrorMessage = "Description shouldn't be null"),
81
+ MinLength(30, ErrorMessage = "Description should be at at least 30 characters"),
82
+ MaxLength(200, ErrorMessage = "Description shouldn't be more than 200 characters")
83
+ ]
84
+ public string Description { get; set; }
85
+
86
+ [Required(ErrorMessage = "StartTime shouldn't be null")]
87
+ public DateTime StartTime { get; set; }
88
+
89
+ [Required(ErrorMessage = "EndTime shouldn't be null")]
90
+ public DateTime EndTime { get; set; }
91
+
92
+ [Required(ErrorMessage = "Availability shouldn't be null")]
93
+ public bool Availability { get; set; }
94
+
95
+ [Range(1.0, double.MaxValue, ErrorMessage = "Price should be greater than zero.")]
96
+ public decimal Price { get; set; }
97
+
98
+ [Range(1, int.MaxValue, ErrorMessage = "Capacity should be greater than zero.")]
99
+ public int Capacity { get; set; }
100
+ }
101
+ }
102
+ }
src/Database/DatabaseContext.cs ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Backend_Teamwork.src.Entities;
2
+ using Backend_Teamwork.src.Utils;
3
+ using Microsoft.EntityFrameworkCore;
4
+ using static Backend_Teamwork.src.Entities.User;
5
+
6
+ namespace Backend_Teamwork.src.Database
7
+ {
8
+ public class DatabaseContext : DbContext
9
+ {
10
+ public DbSet<Category> Category { get; set; }
11
+ public DbSet<Artwork> Artwork { get; set; }
12
+ public DbSet<Order> Order { get; set; }
13
+ public DbSet<OrderDetails> OrderDetail { get; set; }
14
+ public DbSet<Payment> Payment { get; set; }
15
+ public DbSet<Workshop> Workshop { get; set; }
16
+ public DbSet<User> User { get; set; }
17
+ public DbSet<Booking> Booking { get; set; }
18
+
19
+ public DatabaseContext(DbContextOptions option)
20
+ : base(option) { }
21
+
22
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
23
+ {
24
+ modelBuilder.HasPostgresEnum<UserRole>();
25
+ modelBuilder.HasPostgresEnum<Status>();
26
+
27
+ modelBuilder.Entity<User>().HasIndex(x => x.PhoneNumber).IsUnique();
28
+ modelBuilder.Entity<User>().HasIndex(x => x.Email).IsUnique();
29
+
30
+ modelBuilder
31
+ .Entity<User>()
32
+ .HasData(
33
+ new User
34
+ {
35
+ Id = Guid.NewGuid(),
36
+ Name = "Abeer",
37
+ PhoneNumber = "0563034770",
38
+ Email = "abeer@gmail.com",
39
+ Password = PasswordUtils.HashPassword(
40
+ "123",
41
+ out string hashedPassword,
42
+ out byte[] salt
43
+ ),
44
+ Role = UserRole.Admin,
45
+ Salt = salt,
46
+ }
47
+ );
48
+ }
49
+ }
50
+ }
src/Entities/Artwork.cs ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.ComponentModel.DataAnnotations;
2
+
3
+ namespace Backend_Teamwork.src.Entities
4
+ {
5
+ public class Artwork
6
+ {
7
+ public Guid Id { get; set; }
8
+
9
+ [
10
+ Required(ErrorMessage = "Title shouldn't be null"),
11
+ MinLength(6, ErrorMessage = "Title should be at at least 6 characters"),
12
+ MaxLength(30, ErrorMessage = "Title shouldn't be more than 30 characters")
13
+ ]
14
+ public required string Title { get; set; }
15
+
16
+ [
17
+ Required(ErrorMessage = "Description shouldn't be null"),
18
+ MinLength(30, ErrorMessage = "Description should be at at least 30 characters"),
19
+ MaxLength(200, ErrorMessage = "Description shouldn't be more than 200 characters")
20
+ ]
21
+ public required string Description { get; set; }
22
+ public required string ImagUrl { get; set; }
23
+
24
+ [Range(1, int.MaxValue, ErrorMessage = "Quantity should be greater than zero.")]
25
+ public int Quantity { get; set; }
26
+
27
+ [Range(1.0, double.MaxValue, ErrorMessage = "Price should be greater than zero.")]
28
+ public decimal Price { get; set; }
29
+ public DateTime CreatedAt { get; set; }
30
+
31
+ [Required(ErrorMessage = "User Id shouldn't be null")]
32
+ public Guid UserId { get; set; }
33
+ public User User { get; set; } = null!;
34
+
35
+ [Required(ErrorMessage = "Category Id shouldn't be null")]
36
+ public Guid CategoryId { get; set; }
37
+ public Category Category { get; set; } = null!;
38
+
39
+ // OrderDetails
40
+ public List<OrderDetails>? OrderDetails { get; set; }
41
+ }
42
+ }
src/Entities/Booking.cs ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.ComponentModel.DataAnnotations;
2
+ using System.Text.Json.Serialization;
3
+
4
+ namespace Backend_Teamwork.src.Entities
5
+ {
6
+ public class Booking
7
+ {
8
+ public Guid Id { get; set; }
9
+
10
+ [Required(ErrorMessage = "Status shouldn't be null")]
11
+ public Status Status { get; set; }
12
+ public DateTime CreatedAt { get; set; }
13
+
14
+ [Required(ErrorMessage = "Workshop Id shouldn't be null")]
15
+ public Guid WorkshopId { get; set; }
16
+ public Workshop Workshop { get; set; } = null!;
17
+
18
+ [Required(ErrorMessage = "User Id shouldn't be null")]
19
+ public Guid UserId { get; set; }
20
+ public User User { get; set; } = null!;
21
+ public Payment? Payment { get; set; }
22
+ }
23
+
24
+ [JsonConverter(typeof(JsonStringEnumConverter))]
25
+ public enum Status
26
+ {
27
+ Pending,
28
+ Confirmed,
29
+ Canceled,
30
+ Rejected,
31
+ }
32
+ }
src/Entities/Category.cs ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.ComponentModel.DataAnnotations;
2
+
3
+
4
+ namespace Backend_Teamwork.src.Entities
5
+ {
6
+ public class Category
7
+ {
8
+ public Guid Id { set; get; }
9
+ [
10
+ Required(ErrorMessage = "Name shouldn't be null"),
11
+ MinLength(2, ErrorMessage = "Name should be at at least 2 characters"),
12
+ MaxLength(10, ErrorMessage = "Name shouldn't be more than 10 characters")
13
+ ]
14
+ public string Name { set; get; }
15
+ }
16
+ }
src/Entities/Order.cs ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.ComponentModel.DataAnnotations;
2
+
3
+ namespace Backend_Teamwork.src.Entities
4
+ {
5
+ public class Order
6
+ {
7
+ public Guid Id { get; set; }
8
+
9
+ [Range(1.0, double.MaxValue, ErrorMessage = "Total amount should be greater than zero.")]
10
+ public decimal TotalAmount { get; set; }
11
+
12
+ [
13
+ Required(ErrorMessage = "Address shouldn't be null"),
14
+ MinLength(10, ErrorMessage = "Address should be at at least 10 characters"),
15
+ MaxLength(30, ErrorMessage = "Address shouldn't be more than 30 characters")
16
+ ]
17
+ public string ShippingAddress { get; set; }
18
+ public DateTime CreatedAt { get; set; }
19
+
20
+ [Required(ErrorMessage = "User Id shouldn't be null")]
21
+ public Guid UserId { get; set; }
22
+ public User User { get; set; } = null!;
23
+
24
+ [Required(ErrorMessage = "Order details shouldn't be null")]
25
+ public List<OrderDetails> OrderDetails { get; set; }
26
+ public Payment? Payment { get; set; }
27
+ }
28
+ }
src/Entities/OrderDetails.cs ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.ComponentModel.DataAnnotations;
2
+
3
+ namespace Backend_Teamwork.src.Entities
4
+ {
5
+ public class OrderDetails
6
+ {
7
+ public Guid Id { get; set; }
8
+ public Artwork Artwork { get; set; } = null!;
9
+
10
+ [Required(ErrorMessage = "Artwork Id shouldn't be null")]
11
+ public Guid ArtworkId { get; set; }
12
+ public Order Order { get; set; } = null!;
13
+
14
+ [Required(ErrorMessage = "Order Id shouldn't be null")]
15
+ public Guid OrderId { get; set; }
16
+
17
+ [Range(1, int.MaxValue, ErrorMessage = "Quantity should be greater than zero.")]
18
+ public int Quantity { get; set; }
19
+ }
20
+ }
src/Entities/Payment.cs ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.ComponentModel.DataAnnotations;
2
+
3
+ namespace Backend_Teamwork.src.Entities
4
+ {
5
+ public class Payment
6
+ {
7
+ public Guid Id { get; set; }
8
+
9
+ [
10
+ Required(ErrorMessage = "Payment method shouldn't be null"),
11
+ MinLength(10, ErrorMessage = "Payment method should be at at least 10 characters"),
12
+ MaxLength(30, ErrorMessage = "Payment method shouldn't be more than 30 characters")
13
+ ]
14
+ public string PaymentMethod { get; set; }
15
+
16
+ [Range(1.0, double.MaxValue, ErrorMessage = "Amount should be greater than zero.")]
17
+ public decimal Amount { get; set; }
18
+ public DateTime CreatedAt { get; set; }
19
+ public Guid? OrderId { get; set; }
20
+ public Order? Order { get; set; }
21
+ public Guid? BookingId { get; set; }
22
+ public Booking? Booking { get; set; }
23
+ }
24
+ }
src/Entities/User.cs ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.ComponentModel.DataAnnotations;
2
+ using System.Text.Json.Serialization;
3
+
4
+
5
+ namespace Backend_Teamwork.src.Entities
6
+ {
7
+ public class User
8
+ {
9
+ public Guid Id { get; set; }
10
+
11
+ [
12
+ Required(ErrorMessage = "Name shouldn't be null"),
13
+ MinLength(2, ErrorMessage = "Name should be at at least 2 characters"),
14
+ MaxLength(10, ErrorMessage = "Name shouldn't be more than 10 characters")
15
+ ]
16
+ public string Name { get; set; }
17
+
18
+ [
19
+ Required(ErrorMessage = "Phone number shouldn't be null"),
20
+ RegularExpression(
21
+ @"^\+966[5][0-9]{8}$",
22
+ ErrorMessage = "Phone number should be a valid Saudi phone number"
23
+ )
24
+ ]
25
+ public string PhoneNumber { get; set; }
26
+
27
+ [
28
+ Required(ErrorMessage = "Email shouldn't be null"),
29
+ EmailAddress(ErrorMessage = "Email should be with right format: @gmail.com")
30
+ ]
31
+ public string Email { get; set; }
32
+
33
+ [
34
+ Required(ErrorMessage = "Password shouldn't be null."),
35
+ MinLength(8, ErrorMessage = "Password should be at at least 8 characters")
36
+ ]
37
+ public string Password { get; set; }
38
+ public string? Description { set; get; }
39
+
40
+ [Required(ErrorMessage = "Salt shouldn't be null")]
41
+ public byte[]? Salt { get; set; }
42
+
43
+ [Required(ErrorMessage = "Role shouldn't be null")]
44
+ public UserRole Role { get; set; } = UserRole.Customer;
45
+
46
+ [JsonConverter(typeof(JsonStringEnumConverter))]
47
+ public enum UserRole
48
+ {
49
+ Admin,
50
+ Customer,
51
+ Artist,
52
+ }
53
+ }
54
+ }
src/Entities/Workshop.cs ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.ComponentModel.DataAnnotations;
2
+
3
+ namespace Backend_Teamwork.src.Entities
4
+ {
5
+ public class Workshop
6
+ {
7
+ public Guid Id { get; set; }
8
+
9
+ [
10
+ Required(ErrorMessage = "Name shouldn't be null"),
11
+ MinLength(10, ErrorMessage = "Name should be at at least 10 characters"),
12
+ MaxLength(30, ErrorMessage = "Name shouldn't be more than 30 characters")
13
+ ]
14
+ public string Name { get; set; }
15
+
16
+ [
17
+ Required(ErrorMessage = "Location shouldn't be null"),
18
+ MinLength(10, ErrorMessage = "Location should be at at least 10 characters"),
19
+ MaxLength(30, ErrorMessage = "Location shouldn't be more than 30 characters")
20
+ ]
21
+ public string Location { get; set; }
22
+
23
+ [
24
+ Required(ErrorMessage = "Description shouldn't be null"),
25
+ MinLength(30, ErrorMessage = "Description should be at at least 30 characters"),
26
+ MaxLength(200, ErrorMessage = "Description shouldn't be more than 200 characters")
27
+ ]
28
+ public string Description { get; set; }
29
+
30
+ [Required(ErrorMessage = "StartTime shouldn't be null")]
31
+ public DateTime StartTime { get; set; }
32
+
33
+ [Required(ErrorMessage = "EndTime shouldn't be null")]
34
+ public DateTime EndTime { get; set; }
35
+
36
+ [Range(1.0, double.MaxValue, ErrorMessage = "Price should be greater than zero.")]
37
+ public decimal Price { get; set; }
38
+
39
+ [Range(1, int.MaxValue, ErrorMessage = "Capacity should be greater than zero.")]
40
+ public int Capacity { get; set; }
41
+
42
+ [Required(ErrorMessage = "Availability shouldn't be null")]
43
+ public bool Availability { get; set; }
44
+ public DateTime? CreatedAt { get; set; } = DateTime.Now;
45
+
46
+ [Required(ErrorMessage = "User Id shouldn't be null")]
47
+ public Guid UserId { get; set; }
48
+ public User User { get; set; } = null!;
49
+ }
50
+ }
src/Middleware/ErrorHandlerMiddleware.cs ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Backend_Teamwork.src.Utils;
2
+
3
+ namespace Backend_Teamwork.src.Middleware
4
+ {
5
+ public class ErrorHandlerMiddleware
6
+ {
7
+ private readonly RequestDelegate _next;
8
+ public ErrorHandlerMiddleware(RequestDelegate next){
9
+ _next = next;
10
+ }
11
+ public async Task InvokeAsync(HttpContext context){
12
+ try{
13
+ await _next(context);
14
+ }catch(CustomException ex){
15
+ context.Response.StatusCode = ex.StatusCode;
16
+ context.Response.ContentType = "application/json";
17
+ await context.Response.WriteAsync(ex.Message);
18
+ }
19
+ }
20
+ }
21
+ }
src/Program.cs ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.Text;
2
+ using System.Text.Json.Serialization;
3
+ using Backend_Teamwork.src.Database;
4
+ using Backend_Teamwork.src.Entities;
5
+ using Backend_Teamwork.src.Middleware;
6
+ using Backend_Teamwork.src.Repository;
7
+ using Backend_Teamwork.src.Services.artwork;
8
+ using Backend_Teamwork.src.Services.booking;
9
+ using Backend_Teamwork.src.Services.category;
10
+ using Backend_Teamwork.src.Services.order;
11
+ using Backend_Teamwork.src.Services.user;
12
+ using Backend_Teamwork.src.Services.workshop;
13
+ using Backend_Teamwork.src.Utils;
14
+ using Microsoft.AspNetCore.Authentication.JwtBearer;
15
+ using Microsoft.EntityFrameworkCore;
16
+ using Microsoft.IdentityModel.Tokens;
17
+ using Npgsql;
18
+ using static Backend_Teamwork.src.Entities.User;
19
+
20
+ var builder = WebApplication.CreateBuilder(args);
21
+
22
+ //connect to database
23
+ var dataSourceBuilder = new NpgsqlDataSourceBuilder(
24
+ builder.Configuration.GetConnectionString("Local")
25
+ );
26
+ dataSourceBuilder.MapEnum<UserRole>();
27
+ dataSourceBuilder.MapEnum<Status>();
28
+
29
+ //add database connection
30
+ builder.Services.AddDbContext<DatabaseContext>(options =>
31
+ {
32
+ options.UseNpgsql(dataSourceBuilder.Build());
33
+ });
34
+
35
+ //add auto-mapper
36
+ builder.Services.AddAutoMapper(typeof(MapperProfile).Assembly);
37
+
38
+ //add DI services
39
+ builder.Services.AddScoped<ICategoryService, CategoryService>().AddScoped<CategoryRepository>();
40
+ builder.Services.AddScoped<IArtworkService, ArtworkService>().AddScoped<ArtworkRepository>();
41
+ builder.Services.AddScoped<IUserService, UserService>().AddScoped<UserRepository>();
42
+ builder.Services.AddScoped<IOrderService, OrderService>().AddScoped<OrderRepository>();
43
+ builder.Services.AddScoped<IWorkshopService, WorkshopService>().AddScoped<WorkshopRepository>();
44
+ builder.Services.AddScoped<IBookingService, BookingService>().AddScoped<BookingRepository>();
45
+
46
+ //builder.Services.AddScoped<IPaymentService, IPaymentService>().AddScoped<PaymentRepository>();
47
+
48
+
49
+ //add logic for authentication
50
+ builder
51
+ .Services.AddAuthentication(options =>
52
+ {
53
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
54
+ options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
55
+ })
56
+ .AddJwtBearer(Options =>
57
+ {
58
+ Options.TokenValidationParameters = new TokenValidationParameters
59
+ {
60
+ ValidateIssuer = true,
61
+ ValidateAudience = true,
62
+ ValidateLifetime = true,
63
+ ValidateIssuerSigningKey = true,
64
+ ValidIssuer = builder.Configuration["Jwt:Issuer"],
65
+ ValidAudience = builder.Configuration["Jwt:Audience"],
66
+ IssuerSigningKey = new SymmetricSecurityKey(
67
+ Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])
68
+ ),
69
+ };
70
+ });
71
+
72
+ //add logic for athorization
73
+ builder.Services.AddAuthorization(options =>
74
+ {
75
+ options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
76
+ options.AddPolicy("CustomerOnly", policy => policy.RequireRole("Customer"));
77
+ });
78
+
79
+ // *** add CORS settings ***
80
+ builder.Services.AddCors(options =>
81
+ {
82
+ options.AddPolicy("AllowAll",
83
+ policyBuilder => policyBuilder.AllowAnyOrigin()
84
+ .AllowAnyHeader()
85
+ .AllowAnyMethod());
86
+ });
87
+ // builder.Services.AddCors(options =>
88
+ // {
89
+ // options.AddPolicy("AllowSpecificOrigin",
90
+ // builder => builder.WithOrigins("http://localhost:5173")
91
+ // .AllowAnyHeader()
92
+ // .AllowAnyMethod());
93
+ // });
94
+
95
+
96
+ //add controllers
97
+ builder.Services.AddControllers();
98
+ builder.Services.AddEndpointsApiExplorer();
99
+ builder
100
+ .Services.AddControllers()
101
+ .AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
102
+
103
+ //add swagger
104
+ builder.Services.AddSwaggerGen();
105
+
106
+ var app = builder.Build();
107
+ app.UseRouting();
108
+ app.MapGet("/", () => "Server is running");
109
+
110
+ //Convert to Timestamp format
111
+ AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
112
+
113
+ //
114
+
115
+ //test database connection
116
+ using (var scope = app.Services.CreateScope())
117
+ {
118
+ var dbContext = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
119
+ try
120
+ {
121
+ if (dbContext.Database.CanConnect())
122
+ {
123
+ Console.WriteLine("Database is connected");
124
+ dbContext.Database.Migrate();
125
+ }
126
+ else
127
+ {
128
+ Console.WriteLine("Unable to connect to the database.");
129
+ }
130
+ }
131
+ catch (Exception ex)
132
+ {
133
+ Console.WriteLine($"Database connection failed: {ex.Message}");
134
+ }
135
+ }
136
+ app.UseHttpsRedirection();
137
+
138
+ //use middleware
139
+ app.UseMiddleware<ErrorHandlerMiddleware>();
140
+ app.UseAuthentication();
141
+ app.UseAuthorization();
142
+
143
+ //use controllers
144
+ app.MapControllers();
145
+
146
+ //use swagger
147
+ if (app.Environment.IsDevelopment())
148
+ {
149
+ app.UseSwagger();
150
+ app.UseSwaggerUI();
151
+ }
152
+ app.Run();
src/Repository/ArtworkRepository.cs ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Backend_Teamwork.src.Database;
2
+ using Backend_Teamwork.src.Entities;
3
+ using Backend_Teamwork.src.Utils;
4
+ using Microsoft.EntityFrameworkCore;
5
+
6
+ namespace Backend_Teamwork.src.Repository
7
+ {
8
+ public class ArtworkRepository
9
+ {
10
+ private readonly DbSet<Artwork> _artwork;
11
+ private readonly DatabaseContext _databaseContext; // for dependency injection
12
+
13
+ // Dependency Injection
14
+ public ArtworkRepository(DatabaseContext databaseContext)
15
+ {
16
+ _databaseContext = databaseContext;
17
+ // initialize artwork table in the database
18
+ _artwork = databaseContext.Set<Artwork>();
19
+ }
20
+
21
+ // Methods
22
+ // create artwork
23
+ public async Task<Artwork?> CreateOneAsync(Artwork newArtwork)
24
+ {
25
+ await _artwork.AddAsync(newArtwork);
26
+ await _databaseContext.SaveChangesAsync();
27
+ return await GetByIdAsync(newArtwork.Id);
28
+ }
29
+
30
+ // get all artworks
31
+ public async Task<List<Artwork>> GetAllAsync(PaginationOptions paginationOptions)
32
+ {
33
+ var artworkSearch = _artwork.Where(a =>
34
+ a.Title.ToLower().Contains(paginationOptions.Search.ToLower())
35
+ );
36
+
37
+ artworkSearch = artworkSearch.Where(a =>
38
+ a.Price >= paginationOptions.LowPrice && a.Price <= paginationOptions.HighPrice
39
+ );
40
+
41
+ artworkSearch = artworkSearch
42
+ .Skip((paginationOptions.PageNumber - 1) * paginationOptions.PageSize)
43
+ .Take(paginationOptions.PageSize);
44
+
45
+ artworkSearch = paginationOptions.SortOrder switch
46
+ {
47
+ "name_desc" => artworkSearch.OrderByDescending(a => a.Title),
48
+ "date" => artworkSearch.OrderBy(a => a.CreatedAt),
49
+ "date_desc" => artworkSearch.OrderByDescending(a => a.CreatedAt),
50
+ "price" => artworkSearch.OrderBy(a => a.Price),
51
+ "price_desc" => artworkSearch.OrderByDescending(a => a.Price),
52
+ _ => artworkSearch.OrderBy(a => a.Title),
53
+ };
54
+
55
+ return await artworkSearch.Include(o => o.Category).Include(o => o.User).ToListAsync();
56
+ }
57
+
58
+ // get artwork by id
59
+ public async Task<Artwork?> GetByIdAsync(Guid id)
60
+ {
61
+ return await _artwork
62
+ .Include(a => a.Category)
63
+ .Include(a => a.User)
64
+ .FirstOrDefaultAsync(a => a.Id == id);
65
+ }
66
+
67
+ // get artworks by artist id
68
+ public async Task<List<Artwork>> GetByArtistIdAsync(Guid id)
69
+ {
70
+ return await _artwork.Include(a => a.Category).Where(a => a.UserId == id).ToListAsync();
71
+ }
72
+
73
+ // delete artwork
74
+ public async Task<bool> DeleteOneAsync(Artwork artwork)
75
+ {
76
+ _artwork.Remove(artwork);
77
+ await _databaseContext.SaveChangesAsync();
78
+ return true;
79
+ }
80
+
81
+ // update artwork
82
+ public async Task<Artwork?> UpdateOneAsync(Artwork updateArtwork)
83
+ {
84
+ _artwork.Update(updateArtwork);
85
+ await _databaseContext.SaveChangesAsync();
86
+ return await GetByIdAsync(updateArtwork.Id);
87
+ }
88
+
89
+ // Count total artworks
90
+ public async Task<int> CountAsync()
91
+ {
92
+ return await _artwork.CountAsync();
93
+ }
94
+
95
+ // Count artworks by artist
96
+ public async Task<int> CountByArtistAsync(Guid artistId)
97
+ {
98
+ return await _artwork.CountAsync(a => a.UserId == artistId);
99
+ }
100
+ }
101
+ }
src/Repository/BookingRepository.cs ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Backend_Teamwork.src.Database;
2
+ using Backend_Teamwork.src.Entities;
3
+ using Backend_Teamwork.src.Utils;
4
+ using Microsoft.EntityFrameworkCore;
5
+
6
+ namespace Backend_Teamwork.src.Repository
7
+ {
8
+ public class BookingRepository
9
+ {
10
+ private readonly DbSet<Booking> _booking;
11
+ private readonly DatabaseContext _databaseContext;
12
+
13
+ public BookingRepository(DatabaseContext databaseContext)
14
+ {
15
+ _databaseContext = databaseContext;
16
+ _booking = databaseContext.Set<Booking>();
17
+ }
18
+
19
+ public async Task<List<Booking>> GetAllAsync()
20
+ {
21
+ return await _booking
22
+ .Include(b => b.User)
23
+ .Include(b => b.Workshop)
24
+ .ThenInclude(w => w.User)
25
+ .ToListAsync();
26
+ }
27
+
28
+ public async Task<Booking?> GetByIdAsync(Guid id)
29
+ {
30
+ return await _booking
31
+ .Include(b => b.User)
32
+ .Include(b => b.Workshop)
33
+ .ThenInclude(w => w.User)
34
+ .FirstOrDefaultAsync(b => b.Id == id);
35
+ }
36
+
37
+ public async Task<List<Booking>> GetByUserIdAsync(Guid userId)
38
+ {
39
+ return await _booking
40
+ .Include(b => b.User)
41
+ .Include(b => b.Workshop)
42
+ .ThenInclude(w => w.User)
43
+ .Where(b => b.UserId == userId)
44
+ .ToListAsync();
45
+ }
46
+
47
+ public async Task<List<Booking>> GetByStatusAsync(string status)
48
+ {
49
+ return await _booking
50
+ .Include(b => b.User)
51
+ .Include(b => b.Workshop)
52
+ .ThenInclude(w => w.User)
53
+ .Where(b => b.Status.ToString().ToLower() == status.ToLower())
54
+ .ToListAsync();
55
+ }
56
+
57
+ public async Task<List<Booking>> GetByUserIdAndStatusAsync(Guid userId, string status)
58
+ {
59
+ return await _booking
60
+ .Include(b => b.User)
61
+ .Include(b => b.Workshop)
62
+ .ThenInclude(w => w.User)
63
+ .Where(b => b.Status.ToString() == status.ToString() && b.UserId == userId)
64
+ .ToListAsync();
65
+ }
66
+
67
+ public async Task<List<Booking>> GetByWorkshopIdAndStatusAsync(
68
+ Guid workshopId,
69
+ Status status
70
+ )
71
+ {
72
+ return await _booking
73
+ .Where(b => b.WorkshopId == workshopId && b.Status == status)
74
+ .ToListAsync();
75
+ }
76
+
77
+ public async Task<bool> GetByUserIdAndWorkshopIdAsync(Guid userId, Guid workshopId)
78
+ {
79
+ return await _booking.AnyAsync(b => b.UserId == userId && b.WorkshopId == workshopId);
80
+ }
81
+
82
+ public async Task<List<Booking>> GetWithPaginationAsync(PaginationOptions paginationOptions)
83
+ {
84
+ return await _booking
85
+ .Include(b => b.User)
86
+ .Include(b => b.Workshop)
87
+ .ThenInclude(w => w.User)
88
+ .Skip((paginationOptions.PageNumber - 1) * paginationOptions.PageSize)
89
+ .Take(paginationOptions.PageSize)
90
+ .ToListAsync();
91
+ }
92
+
93
+ public async Task<List<Booking>> GetByUserIdWithPaginationAsync(
94
+ PaginationOptions paginationOptions
95
+ )
96
+ {
97
+ var bookings = _booking.Where(b => b.UserId.ToString() == paginationOptions.Search);
98
+ return await bookings
99
+ .Include(b => b.User)
100
+ .Include(b => b.Workshop)
101
+ .ThenInclude(w => w.User)
102
+ .Skip((paginationOptions.PageNumber - 1) * paginationOptions.PageSize)
103
+ .Take(paginationOptions.PageSize)
104
+ .ToListAsync();
105
+ }
106
+
107
+ public async Task<Booking?> CreateAsync(Booking booking)
108
+ {
109
+ await _booking.AddAsync(booking);
110
+ await _databaseContext.SaveChangesAsync();
111
+ return await GetByIdAsync(booking.Id);
112
+ }
113
+
114
+ public async Task<Booking?> UpdateAsync(Booking booking)
115
+ {
116
+ _booking.Update(booking);
117
+ await _databaseContext.SaveChangesAsync();
118
+ return await GetByIdAsync(booking.Id);
119
+ }
120
+ }
121
+ }
src/Repository/CategoryRepository.cs ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Backend_Teamwork.src.Database;
2
+ using Backend_Teamwork.src.Entities;
3
+ using Backend_Teamwork.src.Utils;
4
+ using Microsoft.EntityFrameworkCore;
5
+
6
+ namespace Backend_Teamwork.src.Repository
7
+ {
8
+ public class CategoryRepository
9
+ {
10
+ private readonly DbSet<Category> _category;
11
+ private readonly DatabaseContext _databaseContext;
12
+
13
+ public CategoryRepository(DatabaseContext databaseContext)
14
+ {
15
+ _databaseContext = databaseContext;
16
+ _category = databaseContext.Set<Category>();
17
+ }
18
+
19
+ public async Task<List<Category>> GetAllAsync()
20
+ {
21
+ return await _category.ToListAsync();
22
+ }
23
+
24
+ public async Task<Category?> GetByIdAsync(Guid id)
25
+ {
26
+ return await _category.FirstOrDefaultAsync(c => c.Id == id);
27
+ }
28
+
29
+ public async Task<Category?> GetByNameAsync(string name)
30
+ {
31
+ return await _category.FirstOrDefaultAsync(c => c.Name.ToLower() == name.ToLower());
32
+ }
33
+
34
+ public async Task<List<Category>> GetWithPaginationAsync(PaginationOptions paginationOptions)
35
+ {
36
+ return await _category
37
+ .Skip((paginationOptions.PageNumber - 1) * paginationOptions.PageSize)
38
+ .Take(paginationOptions.PageSize)
39
+ .OrderBy(c => c.Name)
40
+ .ToListAsync();
41
+ }
42
+
43
+ public async Task<List<Category>> SortByNameAsync()
44
+ {
45
+ return await _category.OrderBy(c => c.Name).ToListAsync();
46
+ }
47
+
48
+ public async Task<Category> CreateAsync(Category category)
49
+ {
50
+ await _category.AddAsync(category);
51
+ await _databaseContext.SaveChangesAsync();
52
+ return category;
53
+ }
54
+
55
+ public async Task<Category> UpdateAsync(Category category)
56
+ {
57
+ _category.Update(category);
58
+ await _databaseContext.SaveChangesAsync();
59
+ return category;
60
+ }
61
+
62
+ public async Task DeleteAsync(Category category)
63
+ {
64
+ _category.Remove(category);
65
+ await _databaseContext.SaveChangesAsync();
66
+ }
67
+ }
68
+ }
src/Repository/OrderRepository.cs ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Backend_Teamwork.src.Database;
2
+ using Backend_Teamwork.src.Entities;
3
+ using Backend_Teamwork.src.Utils;
4
+ using Microsoft.EntityFrameworkCore;
5
+
6
+ namespace Backend_Teamwork.src.Repository
7
+ {
8
+ public class OrderRepository
9
+ {
10
+ private readonly DbSet<Order> _order;
11
+ private readonly DatabaseContext _databaseContext;
12
+
13
+ public OrderRepository(DatabaseContext databaseContext)
14
+ {
15
+ _databaseContext = databaseContext;
16
+ _order = databaseContext.Set<Order>();
17
+ }
18
+
19
+ public async Task<List<Order>> GetAllAsync()
20
+ {
21
+ // Include User, OrderDetails, Artwork, and Category
22
+ return await _order
23
+ .Include(o => o.User) // Include User details
24
+ .Include(o => o.OrderDetails)
25
+ .ThenInclude(od => od.Artwork) // Include Artwork
26
+ .ThenInclude(a => a.Category) // Include Category if it's a related entity
27
+ .ToListAsync();
28
+ }
29
+
30
+ public async Task<List<Order>> GetOrdersByUserIdAsync(Guid userId)
31
+ {
32
+ return await _databaseContext
33
+ .Order.Include(o => o.User) // Include User
34
+ .Include(o => o.OrderDetails)
35
+ .ThenInclude(od => od.Artwork) // Include Artwork
36
+ .ThenInclude(a => a.Category) // Include Category in Artwork
37
+ .Where(order => order.UserId == userId)
38
+ .ToListAsync();
39
+ }
40
+
41
+ public async Task<Order?> CreateOneAsync(Order newOrder)
42
+ {
43
+ await _order.AddAsync(newOrder);
44
+ await _databaseContext.SaveChangesAsync();
45
+ return await GetByIdAsync(newOrder.Id);
46
+ }
47
+
48
+ public async Task<Order?> GetByIdAsync(Guid id)
49
+ {
50
+ return await _order
51
+ .Include(o => o.User) // Include User
52
+ .Include(o => o.OrderDetails)
53
+ .ThenInclude(od => od.Artwork) // Include Artwork
54
+ .ThenInclude(a => a.Category) // Include Category in Artwork
55
+ .FirstOrDefaultAsync(o => o.Id == id);
56
+ }
57
+
58
+ public async Task<bool> DeleteOneAsync(Order Order)
59
+ {
60
+ if (Order == null)
61
+ return false;
62
+ _order.Remove(Order);
63
+ await _databaseContext.SaveChangesAsync();
64
+ return true;
65
+ }
66
+
67
+ public async Task<bool> UpdateOneAsync(Order updateOrder)
68
+ {
69
+ if (updateOrder == null)
70
+ return false;
71
+ _order.Update(updateOrder);
72
+ await _databaseContext.SaveChangesAsync();
73
+ return true;
74
+ }
75
+
76
+ public async Task<List<Order>> GetAllAsync(PaginationOptions paginationOptions)
77
+ {
78
+ // Query for orders with optional search
79
+ var orderQuery = _order
80
+ .Include(o => o.OrderDetails) // Include order details
81
+ .Include(o => o.User)
82
+ .Where(o =>
83
+ o.ShippingAddress.Contains(paginationOptions.Search)
84
+ || o.TotalAmount.ToString().Contains(paginationOptions.Search)
85
+ );
86
+
87
+ // Apply pagination
88
+ orderQuery = orderQuery
89
+ .Skip((paginationOptions.PageNumber - 1) * paginationOptions.PageSize)
90
+ .Take(paginationOptions.PageSize);
91
+
92
+ // Sorting logic
93
+ orderQuery = paginationOptions.SortOrder switch
94
+ {
95
+ "amount_desc" => orderQuery.OrderByDescending(o => o.TotalAmount),
96
+ "amount_asc" => orderQuery.OrderBy(o => o.TotalAmount),
97
+ "date_desc" => orderQuery.OrderByDescending(o => o.CreatedAt),
98
+ "date_asc" => orderQuery.OrderBy(o => o.CreatedAt),
99
+ _ => orderQuery.OrderBy(o => o.ShippingAddress), // Default sorting by ShippingAddress
100
+ };
101
+
102
+ return await orderQuery.ToListAsync();
103
+ }
104
+ }
105
+ }
src/Repository/PaymentRepository.cs ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Backend_Teamwork.src.Database;
2
+ using Backend_Teamwork.src.Entities;
3
+ using Microsoft.EntityFrameworkCore;
4
+
5
+ namespace Backend_Teamwork.src.Repository
6
+ {
7
+ public class PaymentRepository
8
+ {
9
+ private readonly DbSet<Payment> _payment;
10
+ private readonly DatabaseContext _databaseContext;
11
+
12
+ public PaymentRepository(DatabaseContext databaseContext)
13
+ {
14
+ _databaseContext = databaseContext;
15
+ _payment = databaseContext.Set<Payment>();
16
+ }
17
+
18
+ // create in database
19
+ public async Task<Payment?> CreateOneAsync(Payment newPayment)
20
+ {
21
+ await _payment.AddAsync(newPayment);
22
+ await _databaseContext.SaveChangesAsync();
23
+ return await GetByIdAsync(newPayment.Id);
24
+ }
25
+
26
+ // get by id
27
+ public async Task<Payment?> GetByIdAsync(Guid id)
28
+ {
29
+ return await _payment
30
+ .Include(p => p.Order)
31
+ .Include(p => p.Booking)
32
+ .FirstOrDefaultAsync(p => p.Id == id);
33
+ }
34
+
35
+ // delete
36
+ public async Task<bool> DeleteOneAsync(Payment deletePayment)
37
+ {
38
+ _payment.Remove(deletePayment);
39
+ await _databaseContext.SaveChangesAsync();
40
+ return true;
41
+ }
42
+
43
+ // update
44
+ public async Task<bool> UpdateOneAsync(Payment updatePayment)
45
+ {
46
+ if (updatePayment == null)
47
+ return false;
48
+ _payment.Update(updatePayment);
49
+ await _databaseContext.SaveChangesAsync();
50
+ return true;
51
+ }
52
+
53
+ // get all
54
+ public async Task<List<Payment>> GetAllAsync()
55
+ {
56
+ return await _payment.Include(p => p.Order).Include(p => p.Booking).ToListAsync();
57
+ }
58
+ }
59
+ }
src/Repository/UserRepository.cs ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Backend_Teamwork.src.Database;
2
+ using Backend_Teamwork.src.Entities;
3
+ using Backend_Teamwork.src.Utils;
4
+ using Microsoft.EntityFrameworkCore;
5
+
6
+ namespace Backend_Teamwork.src.Repository
7
+ {
8
+ public class UserRepository
9
+ {
10
+ private readonly DbSet<User> _user;
11
+ private readonly DatabaseContext _databaseContext;
12
+
13
+ public UserRepository(DatabaseContext databaseContext)
14
+ {
15
+ _databaseContext = databaseContext;
16
+ _user = databaseContext.Set<User>();
17
+ }
18
+
19
+ public async Task<List<User>> GetAllAsync(PaginationOptions paginationOptions)
20
+ {
21
+ // Combined search logic with OR for name, email, or phone number
22
+ var userQuery = _user.Where(a =>
23
+ a.Name.ToLower().Contains(paginationOptions.Search.ToLower())
24
+ || a.Email.ToLower().Contains(paginationOptions.Search.ToLower())
25
+ || a.PhoneNumber.Contains(paginationOptions.Search) // if u try this the dont put the number with +, it does not work
26
+ // I think I need to add the country code in the front-end part for the phone number search.
27
+ );
28
+
29
+ // Apply pagination
30
+ userQuery = userQuery
31
+ .Skip((paginationOptions.PageNumber - 1) * paginationOptions.PageSize)
32
+ .Take(paginationOptions.PageSize);
33
+
34
+ // Sorting logic
35
+ userQuery = paginationOptions.SortOrder switch
36
+ {
37
+ "name_desc" => userQuery.OrderByDescending(a => a.Name),
38
+ "email_desc" => userQuery.OrderByDescending(a => a.Email),
39
+ "email_asc" => userQuery.OrderBy(a => a.Email),
40
+ _ => userQuery.OrderBy(a => a.Name), // Default to ascending by name
41
+ };
42
+
43
+ return await userQuery.ToListAsync();
44
+ }
45
+
46
+ public async Task<int> GetCountAsync()
47
+ {
48
+ return await _user.CountAsync();
49
+ }
50
+
51
+ public async Task<User> CreateOneAsync(User newUser)
52
+ {
53
+ await _user.AddAsync(newUser);
54
+ await _databaseContext.SaveChangesAsync();
55
+ return newUser;
56
+ }
57
+
58
+ public async Task<User?> GetByIdAsync(Guid id)
59
+ {
60
+ return await _user.FindAsync(id);
61
+ }
62
+
63
+ public async Task<bool> DeleteOneAsync(User User)
64
+ {
65
+ if (User == null)
66
+ return false;
67
+ _user.Remove(User);
68
+ await _databaseContext.SaveChangesAsync();
69
+ return true;
70
+ }
71
+
72
+ public async Task<bool> UpdateOneAsync(User updateUser)
73
+ {
74
+ if (updateUser == null)
75
+ return false;
76
+ _user.Update(updateUser);
77
+ await _databaseContext.SaveChangesAsync();
78
+ return true;
79
+ }
80
+
81
+ public async Task<User?> GetByEmailAsync(string email)
82
+ {
83
+ return await _user.FirstOrDefaultAsync(c => c.Email == email);
84
+ }
85
+
86
+ public async Task<User?> GetByPhoneNumberAsync(string phoneNumber)
87
+ {
88
+ return await _user.FirstOrDefaultAsync(c => c.PhoneNumber == phoneNumber);
89
+ }
90
+
91
+ public async Task<User?> GetByNameAsync(string name)
92
+ {
93
+ return await _user.FirstOrDefaultAsync(c => c.Name == name);
94
+ }
95
+ }
96
+ }
src/Repository/WorkshopRepository.cs ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Backend_Teamwork.src.Database;
2
+ using Backend_Teamwork.src.Entities;
3
+ using Backend_Teamwork.src.Utils;
4
+ using Microsoft.EntityFrameworkCore;
5
+
6
+ namespace Backend_Teamwork.src.Repository
7
+ {
8
+ public class WorkshopRepository
9
+ {
10
+ private readonly DbSet<Workshop> _workshops;
11
+ private readonly DatabaseContext _databaseContext;
12
+
13
+ public WorkshopRepository(DatabaseContext databaseContext)
14
+ {
15
+ _databaseContext = databaseContext;
16
+ _workshops = databaseContext.Set<Workshop>();
17
+ }
18
+
19
+ // create in database
20
+ public async Task<Workshop?> CreateOneAsync(Workshop newWorkshop)
21
+ {
22
+ await _workshops.AddAsync(newWorkshop);
23
+ await _databaseContext.SaveChangesAsync();
24
+ return await GetByIdAsync(newWorkshop.Id);
25
+ }
26
+
27
+ // get by id
28
+ public async Task<Workshop?> GetByIdAsync(Guid id)
29
+ {
30
+ return await _workshops.Include(o => o.User).FirstOrDefaultAsync(o => o.Id == id);
31
+ }
32
+
33
+ // delete
34
+ public async Task<bool> DeleteOneAsync(Workshop deleteWorkshop)
35
+ {
36
+ _workshops.Remove(deleteWorkshop);
37
+ await _databaseContext.SaveChangesAsync();
38
+ return true;
39
+ }
40
+
41
+ // update
42
+ public async Task<bool> UpdateOneAsync(Workshop updateWorkshop)
43
+ {
44
+ if (updateWorkshop == null)
45
+ return false;
46
+ _workshops.Update(updateWorkshop);
47
+ await _databaseContext.SaveChangesAsync();
48
+ return true;
49
+ }
50
+
51
+ // get all
52
+ public async Task<List<Workshop>> GetAllAsync()
53
+ {
54
+ return await _workshops.Include(o => o.User).ToListAsync();
55
+ }
56
+
57
+ public async Task<List<Workshop>> GetAllAsync(PaginationOptions paginationOptions)
58
+ {
59
+ // Combined search logic with OR for name, email, or phone number
60
+ var userQuery = _workshops.Where(a =>
61
+ a.Name.ToLower().Contains(paginationOptions.Search.ToLower())
62
+ || a.Location.ToLower().Contains(paginationOptions.Search.ToLower())
63
+ );
64
+
65
+ // Apply pagination
66
+ userQuery = userQuery
67
+ .Skip((paginationOptions.PageNumber - 1) * paginationOptions.PageSize)
68
+ .Take(paginationOptions.PageSize);
69
+
70
+ // Apply sorting logic
71
+ userQuery = paginationOptions.SortOrder switch
72
+ {
73
+ "name_desc" => userQuery.OrderByDescending(a => a.Name),
74
+ "location_asc" => userQuery.OrderBy(a => a.Location),
75
+ "location_desc" => userQuery.OrderByDescending(a => a.Location),
76
+ "price_desc" => userQuery.OrderByDescending(a => a.Price),
77
+ "price_asc" => userQuery.OrderBy(a => a.Price),
78
+ "date_desc" => userQuery.OrderByDescending(a => a.CreatedAt),
79
+ "date_asc" => userQuery.OrderBy(a => a.CreatedAt),
80
+ "capacity_desc" => userQuery.OrderByDescending(a => a.Capacity),
81
+ _ => userQuery.OrderBy(a => a.Name), // Default to ascending by name
82
+ };
83
+
84
+ return await userQuery.ToListAsync();
85
+ }
86
+ }
87
+ }
src/Services/artwork/ArtworkService.cs ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using AutoMapper;
2
+ using Backend_Teamwork.src.Entities;
3
+ using Backend_Teamwork.src.Repository;
4
+ using Backend_Teamwork.src.Utils;
5
+ using static Backend_Teamwork.src.DTO.ArtworkDTO;
6
+ using static Backend_Teamwork.src.Entities.User;
7
+
8
+ namespace Backend_Teamwork.src.Services.artwork
9
+ {
10
+ public class ArtworkService : IArtworkService
11
+ {
12
+ private readonly ArtworkRepository _artworkRepo;
13
+ private readonly UserRepository _userRepo;
14
+ private readonly CategoryRepository _categoryRepo;
15
+ private readonly IMapper _mapper;
16
+
17
+ public ArtworkService(
18
+ ArtworkRepository artworkRepo,
19
+ UserRepository userRepo,
20
+ CategoryRepository categoryRepo,
21
+ IMapper mapper
22
+ )
23
+ {
24
+ _artworkRepo = artworkRepo;
25
+ _userRepo = userRepo;
26
+ _categoryRepo = categoryRepo;
27
+ _mapper = mapper;
28
+ }
29
+
30
+ public async Task<ArtworkReadDto> CreateOneAsync(Guid artistId, ArtworkCreateDto createDto)
31
+ {
32
+ var foundCategory = await _categoryRepo.GetByIdAsync(createDto.CategoryId);
33
+ if (foundCategory == null)
34
+ {
35
+ throw CustomException.NotFound($"Category with id: {createDto.CategoryId} not found");
36
+ }
37
+ var artwork = _mapper.Map<ArtworkCreateDto, Artwork>(createDto);
38
+ artwork.UserId = artistId;
39
+ var createdArtwork = await _artworkRepo.CreateOneAsync(artwork);
40
+ return _mapper.Map<Artwork, ArtworkReadDto>(createdArtwork);
41
+ }
42
+
43
+ public async Task<List<ArtworkReadDto>> GetAllAsync(PaginationOptions paginationOptions)
44
+ {
45
+ if (paginationOptions.PageSize <= 0)
46
+ {
47
+ throw CustomException.BadRequest("PageSize should be greater than 0.");
48
+ }
49
+
50
+ if (paginationOptions.PageNumber <= 0)
51
+ {
52
+ throw CustomException.BadRequest("PageNumber should be greater than 0.");
53
+ }
54
+
55
+ var artworkList = await _artworkRepo.GetAllAsync(paginationOptions);
56
+ if (artworkList == null || !artworkList.Any())
57
+ {
58
+ throw CustomException.NotFound("No artworks found.");
59
+ }
60
+ return _mapper.Map<List<Artwork>, List<ArtworkReadDto>>(artworkList);
61
+ }
62
+
63
+ public async Task<ArtworkReadDto> GetByIdAsync(Guid id)
64
+ {
65
+ var artwork = await _artworkRepo.GetByIdAsync(id);
66
+ if (artwork == null)
67
+ {
68
+ throw CustomException.NotFound($"Artwork with id: {id} not found");
69
+ }
70
+ return _mapper.Map<Artwork, ArtworkReadDto>(artwork);
71
+ }
72
+
73
+ public async Task<List<ArtworkReadDto>> GetByArtistIdAsync(Guid id)
74
+ {
75
+ var user = await _userRepo.GetByIdAsync(id)
76
+ ?? throw CustomException.NotFound($"User with id: {id} not found");
77
+ if (user.Role.ToString() != UserRole.Artist.ToString())
78
+ {
79
+ throw CustomException.BadRequest($"User with id: {id} is not an Artist");
80
+ }
81
+
82
+ var artworks = await _artworkRepo.GetByArtistIdAsync(id)
83
+ ?? throw CustomException.NotFound($"Artist with id: {id} has no artworks");
84
+
85
+ return _mapper.Map<List<Artwork>, List<ArtworkReadDto>>(artworks);
86
+ }
87
+
88
+ public async Task<bool> DeleteOneAsync(Guid id)
89
+ {
90
+ var foundArtwork = await _artworkRepo.GetByIdAsync(id);
91
+ if (foundArtwork == null)
92
+ {
93
+ throw CustomException.NotFound($"Artwork with id: {id} not found");
94
+ }
95
+ bool isDeleted = await _artworkRepo.DeleteOneAsync(foundArtwork);
96
+ return isDeleted;
97
+ }
98
+
99
+ public async Task<ArtworkReadDto> UpdateOneAsync(Guid id, ArtworkUpdateDTO updateDto)
100
+ {
101
+ var foundArtwork = await _artworkRepo.GetByIdAsync(id);
102
+ if (foundArtwork == null)
103
+ {
104
+ throw CustomException.NotFound($"Artwork with id: {id} not found");
105
+ }
106
+
107
+ _mapper.Map(updateDto, foundArtwork);
108
+ var updatedArtwork = await _artworkRepo.UpdateOneAsync(foundArtwork);
109
+ return _mapper.Map<Artwork, ArtworkReadDto>(updatedArtwork);
110
+ }
111
+
112
+ public async Task<int> GetArtworkCountAsync()
113
+ {
114
+ return await _artworkRepo.CountAsync();
115
+ }
116
+
117
+ public async Task<int> GetArtworkCountByArtistAsync(Guid artistId)
118
+ {
119
+ return await _artworkRepo.CountByArtistAsync(artistId);
120
+ }
121
+ }
122
+ }
src/Services/artwork/IArtworkService.cs ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Backend_Teamwork.src.Utils;
2
+ using static Backend_Teamwork.src.DTO.ArtworkDTO;
3
+
4
+ namespace Backend_Teamwork.src.Services.artwork
5
+ {
6
+ public interface IArtworkService
7
+ {
8
+ Task<ArtworkReadDto> CreateOneAsync(Guid userId, ArtworkCreateDto artwork);
9
+ Task<List<ArtworkReadDto>> GetAllAsync(PaginationOptions paginationOptions);
10
+ Task<ArtworkReadDto> GetByIdAsync(Guid id);
11
+ Task<List<ArtworkReadDto>> GetByArtistIdAsync(Guid id);
12
+ Task<bool> DeleteOneAsync(Guid id);
13
+ Task<ArtworkReadDto> UpdateOneAsync(Guid id, ArtworkUpdateDTO updateArtwork);
14
+
15
+ // New methods for counting
16
+ Task<int> GetArtworkCountAsync(); // Get total count of artworks
17
+ Task<int> GetArtworkCountByArtistAsync(Guid artistId); // Get count of artworks by a specific artist
18
+ }
19
+ }
src/Services/booking/BookingService.cs ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using AutoMapper;
2
+ using Backend_Teamwork.src.Entities;
3
+ using Backend_Teamwork.src.Repository;
4
+ using Backend_Teamwork.src.Utils;
5
+ using static Backend_Teamwork.src.DTO.BookingDTO;
6
+ using static Backend_Teamwork.src.Entities.User;
7
+
8
+ namespace Backend_Teamwork.src.Services.booking
9
+ {
10
+ public class BookingService : IBookingService
11
+ {
12
+ private readonly BookingRepository _bookingRepository;
13
+ private readonly WorkshopRepository _workshopRepository;
14
+ private readonly PaymentRepository _paymentRepository;
15
+
16
+ private readonly IMapper _mapper;
17
+
18
+ public BookingService(
19
+ BookingRepository bookingRepository,
20
+ WorkshopRepository workshopRepository,
21
+ IMapper mapper
22
+ )
23
+ {
24
+ _bookingRepository = bookingRepository;
25
+ _workshopRepository = workshopRepository;
26
+ _mapper = mapper;
27
+ }
28
+
29
+ public async Task<List<BookingReadDto>> GetAllAsync()
30
+ {
31
+ var bookings = await _bookingRepository.GetAllAsync();
32
+ if (bookings.Count == 0)
33
+ {
34
+ throw CustomException.NotFound($"Bookings not found");
35
+ }
36
+ return _mapper.Map<List<Booking>, List<BookingReadDto>>(bookings);
37
+ }
38
+
39
+ public async Task<BookingReadDto> GetByIdAsync(Guid id, Guid userId, string userRole)
40
+ {
41
+ var booking = await _bookingRepository.GetByIdAsync(id);
42
+ if (booking == null)
43
+ {
44
+ throw CustomException.NotFound($"Booking with id: {id} not found");
45
+ }
46
+ if (userRole != UserRole.Admin.ToString() && booking.UserId != userId)
47
+ {
48
+ throw CustomException.Forbidden($"Not allowed to access booking with id: {id}");
49
+ }
50
+ return _mapper.Map<Booking, BookingReadDto>(booking);
51
+ }
52
+
53
+ public async Task<List<BookingReadDto>> GetByUserIdAsync(Guid userId)
54
+ {
55
+ var bookings = await _bookingRepository.GetByUserIdAsync(userId);
56
+ if (bookings.Count == 0)
57
+ {
58
+ throw CustomException.NotFound(
59
+ $"Bookings associated with userId: {userId} not found"
60
+ );
61
+ }
62
+ return _mapper.Map<List<Booking>, List<BookingReadDto>>(bookings);
63
+ }
64
+
65
+ public async Task<List<BookingReadDto>> GetByStatusAsync(string status)
66
+ {
67
+ var bookings = await _bookingRepository.GetByStatusAsync(status);
68
+ if (bookings.Count == 0)
69
+ {
70
+ throw CustomException.NotFound($"Bookings with status: {status} not found");
71
+ }
72
+ return _mapper.Map<List<Booking>, List<BookingReadDto>>(bookings);
73
+ }
74
+
75
+ public async Task<List<BookingReadDto>> GetByUserIdAndStatusAsync(
76
+ Guid userId,
77
+ string status
78
+ )
79
+ {
80
+ var bookings = await _bookingRepository.GetByUserIdAndStatusAsync(userId, status);
81
+ if (bookings.Count == 0)
82
+ {
83
+ throw CustomException.NotFound($"Bookings with status: {status} not found");
84
+ }
85
+ return _mapper.Map<List<Booking>, List<BookingReadDto>>(bookings);
86
+ }
87
+
88
+ public async Task<List<BookingReadDto>> GetWithPaginationAsync(
89
+ PaginationOptions paginationOptions
90
+ )
91
+ {
92
+ var bookings = await _bookingRepository.GetWithPaginationAsync(paginationOptions);
93
+ if (bookings.Count == 0)
94
+ {
95
+ throw CustomException.NotFound($"Bookings not found");
96
+ }
97
+ return _mapper.Map<List<Booking>, List<BookingReadDto>>(bookings);
98
+ }
99
+
100
+ public async Task<List<BookingReadDto>> GetByUserIdWithPaginationAsync(
101
+ PaginationOptions paginationOptions
102
+ )
103
+ {
104
+ var bookings = await _bookingRepository.GetByUserIdWithPaginationAsync(
105
+ paginationOptions
106
+ );
107
+ if (bookings.Count == 0)
108
+ {
109
+ throw CustomException.NotFound($"Bookings not found");
110
+ }
111
+ return _mapper.Map<List<Booking>, List<BookingReadDto>>(bookings);
112
+ }
113
+
114
+ public async Task<BookingReadDto> CreateAsync(BookingCreateDto booking, Guid userId)
115
+ {
116
+ //1. check if the workshop is found
117
+ var workshop = await _workshopRepository.GetByIdAsync(booking.WorkshopId);
118
+ if (workshop == null)
119
+ {
120
+ throw CustomException.NotFound($"Workshp with id: {booking.WorkshopId} not found");
121
+ }
122
+ //2. check if the workshop isn't available
123
+ if (!workshop.Availability)
124
+ {
125
+ throw CustomException.BadRequest($"Invalid booking");
126
+ }
127
+ //3. check if the user already enrolled in this workshop
128
+ bool isFound = await _bookingRepository.GetByUserIdAndWorkshopIdAsync(
129
+ userId,
130
+ booking.WorkshopId
131
+ );
132
+ if (isFound)
133
+ {
134
+ throw CustomException.BadRequest($"Invalid booking");
135
+ }
136
+ //4. check if the user enrolled in other workshop at the same time
137
+ var workshops = await _workshopRepository.GetAllAsync();
138
+ var foundWorkshop = workshops.FirstOrDefault(w =>
139
+ (w.StartTime == workshop.StartTime && w.EndTime == workshop.EndTime)
140
+ || (w.StartTime < workshop.StartTime && w.EndTime > workshop.StartTime)
141
+ || (w.StartTime < workshop.EndTime && w.EndTime > workshop.EndTime)
142
+ );
143
+ var isFound2 = false;
144
+ if (foundWorkshop != null)
145
+ {
146
+ isFound2 = await _bookingRepository.GetByUserIdAndWorkshopIdAsync(
147
+ userId,
148
+ foundWorkshop.Id
149
+ );
150
+ }
151
+ if (isFound2)
152
+ {
153
+ throw CustomException.BadRequest($"Invalid booking");
154
+ }
155
+ //create booking
156
+ var mappedBooking = _mapper.Map<BookingCreateDto, Booking>(booking);
157
+ mappedBooking.UserId = userId;
158
+ mappedBooking.Status = Status.Pending;
159
+ var createdBooking = await _bookingRepository.CreateAsync(mappedBooking);
160
+ return _mapper.Map<Booking, BookingReadDto>(createdBooking);
161
+ }
162
+
163
+ //after payment
164
+ public async Task<BookingReadDto> ConfirmAsync(Guid id)
165
+ {
166
+ var booking = await _bookingRepository.GetByIdAsync(id);
167
+ if (booking == null)
168
+ {
169
+ throw CustomException.NotFound($"Booking with id: {id} not found");
170
+ }
171
+ //1. check if the workshop isn't available
172
+ if (!booking.Workshop.Availability)
173
+ {
174
+ throw CustomException.BadRequest($"Invalid confirming");
175
+ }
176
+ //2. check if the booking status isn't pending
177
+ if (booking.Status.ToString() != Status.Pending.ToString())
178
+ {
179
+ throw CustomException.BadRequest($"Invalid confirming");
180
+ }
181
+ //3. check if the user doesn't pay
182
+ //var payment =
183
+
184
+ //confirm booking
185
+ booking.Status = Status.Confirmed;
186
+ var updatedBooking = await _bookingRepository.UpdateAsync(booking);
187
+ return _mapper.Map<Booking, BookingReadDto>(updatedBooking);
188
+ }
189
+
190
+ //after workshop becomes unavailable
191
+ public async Task<List<BookingReadDto>> RejectAsync(Guid workshopId)
192
+ {
193
+ var workshop = await _workshopRepository.GetByIdAsync(workshopId);
194
+ //1. check if the workshop is found
195
+ if (workshop == null)
196
+ {
197
+ throw CustomException.NotFound($"Workshp with id: {workshopId} not found");
198
+ }
199
+ //2. check if the workshop is available
200
+ if (workshop.Availability)
201
+ {
202
+ throw CustomException.BadRequest($"Invalid regecting");
203
+ }
204
+ var bookings = await _bookingRepository.GetByWorkshopIdAndStatusAsync(
205
+ workshopId,
206
+ Status.Pending
207
+ );
208
+ //3. check if there is booking with Pending Status
209
+ if (bookings == null)
210
+ {
211
+ throw CustomException.BadRequest($"Invalid regecting");
212
+ }
213
+ foreach (var booking in bookings)
214
+ {
215
+ //reject booking
216
+ booking.Status = Status.Rejected;
217
+ var updatedBooking = await _bookingRepository.UpdateAsync(booking);
218
+ }
219
+ return _mapper.Map<List<Booking>, List<BookingReadDto>>(bookings);
220
+ }
221
+
222
+ public async Task<BookingReadDto> CancelAsync(Guid id, Guid userId)
223
+ {
224
+ var booking = await _bookingRepository.GetByIdAsync(id);
225
+ if (booking == null)
226
+ {
227
+ throw CustomException.NotFound($"Booking with id: {id} not found");
228
+ }
229
+ //1. check if the booking belongs to the user
230
+ if (booking.UserId != userId)
231
+ {
232
+ throw CustomException.BadRequest($"Invalid canceling");
233
+ }
234
+ //2. check if the workshop is available
235
+ if (!booking.Workshop.Availability)
236
+ {
237
+ throw CustomException.BadRequest($"Invalid canceling");
238
+ }
239
+ //3. check if the booking status isn't pending
240
+ if (booking.Status.ToString() != Status.Pending.ToString())
241
+ {
242
+ throw CustomException.BadRequest($"Invalid canceling");
243
+ }
244
+ //4. check if the user pay
245
+ //var payment =
246
+
247
+ //Cancel booking
248
+ booking.Status = Status.Canceled;
249
+ var updatedBooking = await _bookingRepository.UpdateAsync(booking);
250
+ return _mapper.Map<Booking, BookingReadDto>(updatedBooking);
251
+ }
252
+ }
253
+ }