first commit
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .dockerignore +25 -0
- .gitignore +7 -0
- Backend.csproj +26 -0
- Backend.sln +25 -0
- Dockerfile +24 -0
- Properties/launchSettings.json +41 -0
- appsettings.Development.json +8 -0
- d.cmd +3 -0
- d.sh +3 -0
- docker-compose.debug.yml +18 -0
- docker-compose.yml +12 -0
- p.md +250 -0
- run.cmd +2 -0
- screenshots/Artify ERD.png +0 -0
- src/Controllers/ArtworksController.cs +129 -0
- src/Controllers/BookingsController.cs +156 -0
- src/Controllers/CategoriesController.cs +90 -0
- src/Controllers/OrdersController.cs +126 -0
- src/Controllers/PaymentsController.cs +60 -0
- src/Controllers/UsersController.cs +133 -0
- src/Controllers/WorkshopsController.cs +86 -0
- src/DTO/ArtworkDTO.cs +79 -0
- src/DTO/BookingDTO.cs +24 -0
- src/DTO/CategoryDTO.cs +33 -0
- src/DTO/OrderDTO.cs +53 -0
- src/DTO/OrderDetailDTO.cs +30 -0
- src/DTO/PaymentDTO.cs +47 -0
- src/DTO/UserDTO.cs +98 -0
- src/DTO/WorkshopDTO.cs +102 -0
- src/Database/DatabaseContext.cs +50 -0
- src/Entities/Artwork.cs +42 -0
- src/Entities/Booking.cs +32 -0
- src/Entities/Category.cs +16 -0
- src/Entities/Order.cs +28 -0
- src/Entities/OrderDetails.cs +20 -0
- src/Entities/Payment.cs +24 -0
- src/Entities/User.cs +54 -0
- src/Entities/Workshop.cs +50 -0
- src/Middleware/ErrorHandlerMiddleware.cs +21 -0
- src/Program.cs +152 -0
- src/Repository/ArtworkRepository.cs +101 -0
- src/Repository/BookingRepository.cs +121 -0
- src/Repository/CategoryRepository.cs +68 -0
- src/Repository/OrderRepository.cs +105 -0
- src/Repository/PaymentRepository.cs +59 -0
- src/Repository/UserRepository.cs +96 -0
- src/Repository/WorkshopRepository.cs +87 -0
- src/Services/artwork/ArtworkService.cs +122 -0
- src/Services/artwork/IArtworkService.cs +19 -0
- 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 |
+
}
|