diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..3dbbcf3e707bbeadeca29139f67694b695a211ab
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,25 @@
+**/.classpath
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/bin
+**/charts
+**/docker-compose*
+**/compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..271fe8365b4e7641c2e4488f02c952d082b16e6b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+.vscode
+.DS_Store
+bin
+obj
+*.http
+appsettings.json
+Migrations
diff --git a/Backend.csproj b/Backend.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..309d704c5036830993fa7ab38285857f29e916b9
--- /dev/null
+++ b/Backend.csproj
@@ -0,0 +1,26 @@
+
+
+
+ net8.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
diff --git a/Backend.sln b/Backend.sln
new file mode 100644
index 0000000000000000000000000000000000000000..c7196e72ffea412328899acb9e58d57e5c054bd8
--- /dev/null
+++ b/Backend.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.002.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend", "Backend.csproj", "{098C6FA2-0AD6-4DB6-A189-6F70D6E3362B}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {098C6FA2-0AD6-4DB6-A189-6F70D6E3362B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {098C6FA2-0AD6-4DB6-A189-6F70D6E3362B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {098C6FA2-0AD6-4DB6-A189-6F70D6E3362B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {098C6FA2-0AD6-4DB6-A189-6F70D6E3362B}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {A69E6D17-A6C9-4FBB-97A3-7ED53714E982}
+ EndGlobalSection
+EndGlobal
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..2febe4cf7c5d75176ab089f70f651ed13a2decc4
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,24 @@
+FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
+WORKDIR /app
+EXPOSE 5125
+
+ENV ASPNETCORE_URLS=http://0.0.0.0:5125
+# ENV ConnectionStrings__Local="Host=aws-0-eu-central-1.pooler.supabase.com;Port=6543;Database=postgres;Username=postgres.xergfpyauvdbavdyzzbk;Password=009988Ppooii@@@@"
+ENV ConnectionStrings__Local="Host=aws-0-eu-central-1.pooler.supabase.com;Port=6543;Database=postgres;Username=postgres.hpbtdersdvvpxrkmhbfe;Password=009988Ppooii@@@@"
+ENV JWT__KEY="your-long-secret-key"
+ENV JWT__ISSUER="your-issuer"
+ENV JWT__AUDIENCE="your-audience"
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+WORKDIR /src
+COPY ["Backend.csproj", "./"]
+RUN dotnet restore "Backend.csproj"
+COPY . .
+RUN dotnet build "Backend.csproj" -c Release -o /app/build
+
+FROM build AS publish
+RUN dotnet publish "Backend.csproj" -c Release -o /app/publish
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "Backend.dll"]
\ No newline at end of file
diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json
new file mode 100644
index 0000000000000000000000000000000000000000..894e789abcd320251dd8e640c6dd7e91ddd8107c
--- /dev/null
+++ b/Properties/launchSettings.json
@@ -0,0 +1,41 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:58899",
+ "sslPort": 44376
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "http://localhost:5125",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "https://localhost:7097;http://localhost:5125",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/appsettings.Development.json b/appsettings.Development.json
new file mode 100644
index 0000000000000000000000000000000000000000..0c208ae9181e5e5717e47ec1bd59368aebc6066e
--- /dev/null
+++ b/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/d.cmd b/d.cmd
new file mode 100644
index 0000000000000000000000000000000000000000..fd6fc47609791695fa1c3437cab7f3d1555664eb
--- /dev/null
+++ b/d.cmd
@@ -0,0 +1,3 @@
+git add .
+git commit -m "first commit"
+git push -u origin main
\ No newline at end of file
diff --git a/d.sh b/d.sh
new file mode 100755
index 0000000000000000000000000000000000000000..fd6fc47609791695fa1c3437cab7f3d1555664eb
--- /dev/null
+++ b/d.sh
@@ -0,0 +1,3 @@
+git add .
+git commit -m "first commit"
+git push -u origin main
\ No newline at end of file
diff --git a/docker-compose.debug.yml b/docker-compose.debug.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1d43ccd12cb0438462e010a63e05e8b2d6895fc8
--- /dev/null
+++ b/docker-compose.debug.yml
@@ -0,0 +1,18 @@
+# Please refer https://aka.ms/HTTPSinContainer on how to setup an https developer certificate for your ASP.NET Core service.
+
+version: '3.4'
+
+services:
+ backend:
+ image: backend
+ build:
+ context: .
+ dockerfile: ./Dockerfile
+ args:
+ - configuration=Debug
+ ports:
+ - 5125:5125
+ environment:
+ - ASPNETCORE_ENVIRONMENT=Development
+ volumes:
+ - ~/.vsdbg:c:\remote_debugger:rw
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b6c1972b1eeb93718964b46de3ad0f0004eb353e
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,12 @@
+# Please refer https://aka.ms/HTTPSinContainer on how to setup an https developer certificate for your ASP.NET Core service.
+
+version: '3.4'
+
+services:
+ backend:
+ image: backend
+ build:
+ context: .
+ dockerfile: ./Dockerfile
+ ports:
+ - 5125:5125
diff --git a/p.md b/p.md
new file mode 100644
index 0000000000000000000000000000000000000000..54d9b64de229c2a0643bd59954a66adaf993eb19
--- /dev/null
+++ b/p.md
@@ -0,0 +1,250 @@
+Unhandled exception. System.ArgumentException: Host can't be null
+ at Npgsql.NpgsqlConnectionStringBuilder.PostProcessAndValidate()
+ at Npgsql.NpgsqlSlimDataSourceBuilder.PrepareConfiguration()
+ at Npgsql.NpgsqlSlimDataSourceBuilder.Build()
+ at Npgsql.NpgsqlDataSourceBuilder.Build()
+ at Program.<>c__DisplayClass0_0.<$>b__0(DbContextOptionsBuilder options) in /src/src/Program.cs:line 32
+ at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.<>c__DisplayClass1_0`2.b__0(IServiceProvider _, DbContextOptionsBuilder b)
+ at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.CreateDbContextOptions[TContext](IServiceProvider applicationServiceProvider, Action`2 optionsAction)
+ at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.<>c__DisplayClass17_0`1.b__0(IServiceProvider p)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.b__0(ServiceProviderEngineScope scope)
+ at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
+ at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
+ at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
+ at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.<>c__17`1.b__17_1(IServiceProvider p)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.b__0(ServiceProviderEngineScope scope)
+ at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
+ at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
+ at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
+ at Program.$(String[] args) in /src/src/Program.cs:line 111
+==> Exited with status 139
+==> Common ways to troubleshoot your deploy: https://docs.render.com/troubleshooting-deploys
+Unhandled exception. System.ArgumentException: Host can't be null
+ at Npgsql.NpgsqlConnectionStringBuilder.PostProcessAndValidate()
+ at Npgsql.NpgsqlSlimDataSourceBuilder.PrepareConfiguration()
+ at Npgsql.NpgsqlSlimDataSourceBuilder.Build()
+ at Npgsql.NpgsqlDataSourceBuilder.Build()
+ at Program.<>c__DisplayClass0_0.<$>b__0(DbContextOptionsBuilder options) in /src/src/Program.cs:line 32
+ at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.<>c__DisplayClass1_0`2.b__0(IServiceProvider _, DbContextOptionsBuilder b)
+ at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.CreateDbContextOptions[TContext](IServiceProvider applicationServiceProvider, Action`2 optionsAction)
+ at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.<>c__DisplayClass17_0`1.b__0(IServiceProvider p)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.b__0(ServiceProviderEngineScope scope)
+ at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
+ at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
+ at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
+ at Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.<>c__17`1.b__17_1(IServiceProvider p)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.b__0(ServiceProviderEngineScope scope)
+ at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
+ at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
+ at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
+ at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
+ at Program.$(String[] args) in /src/src/Program.cs:line 111
+
+
+
+ FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
+WORKDIR /app
+EXPOSE 5125
+
+ENV ASPNETCORE_URLS=http://0.0.0.0:5125
+
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+ARG configuration=Release
+WORKDIR /src
+COPY ["Backend.csproj", "./"]
+RUN dotnet restore "Backend.csproj"
+COPY . .
+WORKDIR "/src/."
+RUN dotnet build "Backend.csproj" -c $configuration -o /app/build
+
+FROM build AS publish
+ARG configuration=Release
+
+RUN dotnet publish "Backend.csproj" -c $configuration -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "Backend.dll"]
+
+
+using System.Text;
+using System.Text.Json.Serialization;
+using Backend_Teamwork.src.Database;
+using Backend_Teamwork.src.Entities;
+using Backend_Teamwork.src.Middleware;
+using Backend_Teamwork.src.Repository;
+using Backend_Teamwork.src.Services.artwork;
+using Backend_Teamwork.src.Services.booking;
+using Backend_Teamwork.src.Services.category;
+using Backend_Teamwork.src.Services.order;
+using Backend_Teamwork.src.Services.user;
+using Backend_Teamwork.src.Services.workshop;
+using Backend_Teamwork.src.Utils;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.IdentityModel.Tokens;
+using Npgsql;
+using static Backend_Teamwork.src.Entities.User;
+
+var builder = WebApplication.CreateBuilder(args);
+
+//connect to database
+var dataSourceBuilder = new NpgsqlDataSourceBuilder(
+ builder.Configuration.GetConnectionString("Local")
+);
+dataSourceBuilder.MapEnum();
+dataSourceBuilder.MapEnum();
+
+//add database connection
+builder.Services.AddDbContext(options =>
+{
+ options.UseNpgsql(dataSourceBuilder.Build());
+});
+
+//add auto-mapper
+builder.Services.AddAutoMapper(typeof(MapperProfile).Assembly);
+
+//add DI services
+builder.Services.AddScoped().AddScoped();
+builder.Services.AddScoped().AddScoped();
+builder.Services.AddScoped().AddScoped();
+builder.Services.AddScoped().AddScoped();
+builder.Services.AddScoped().AddScoped();
+builder.Services.AddScoped().AddScoped();
+
+//builder.Services.AddScoped().AddScoped();
+
+
+//add logic for authentication
+builder
+ .Services.AddAuthentication(options =>
+ {
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+ options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
+ })
+ .AddJwtBearer(Options =>
+ {
+ Options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidateIssuer = true,
+ ValidateAudience = true,
+ ValidateLifetime = true,
+ ValidateIssuerSigningKey = true,
+ ValidIssuer = builder.Configuration["Jwt:Issuer"],
+ ValidAudience = builder.Configuration["Jwt:Audience"],
+ IssuerSigningKey = new SymmetricSecurityKey(
+ Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])
+ ),
+ };
+ });
+
+//add logic for athorization
+builder.Services.AddAuthorization(options =>
+{
+ options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
+ options.AddPolicy("CustomerOnly", policy => policy.RequireRole("Customer"));
+});
+
+// *** add CORS settings ***
+builder.Services.AddCors(options =>
+{
+ options.AddPolicy("AllowSpecificOrigin",
+ builder => builder.WithOrigins("http://localhost:5173")
+ .AllowAnyHeader()
+ .AllowAnyMethod());
+});
+
+
+//add controllers
+builder.Services.AddControllers();
+builder.Services.AddEndpointsApiExplorer();
+builder
+ .Services.AddControllers()
+ .AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
+
+//add swagger
+builder.Services.AddSwaggerGen();
+
+var app = builder.Build();
+app.UseRouting();
+app.MapGet("/", () => "Server is running");
+
+//Convert to Timestamp format
+AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
+
+//
+
+//test database connection
+using (var scope = app.Services.CreateScope())
+{
+ var dbContext = scope.ServiceProvider.GetRequiredService();
+ try
+ {
+ if (dbContext.Database.CanConnect())
+ {
+ Console.WriteLine("Database is connected");
+ }
+ else
+ {
+ Console.WriteLine("Unable to connect to the database.");
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Database connection failed: {ex.Message}");
+ }
+}
+app.UseHttpsRedirection();
+
+//use middleware
+app.UseMiddleware();
+app.UseAuthentication();
+app.UseAuthorization();
+
+//use controllers
+app.MapControllers();
+
+//use swagger
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+app.Run();
+
+
+
diff --git a/run.cmd b/run.cmd
new file mode 100644
index 0000000000000000000000000000000000000000..0a5d8b3a6e067e5b9e412c74e4859794ea57dd13
--- /dev/null
+++ b/run.cmd
@@ -0,0 +1,2 @@
+cd frontend
+npm run dev
\ No newline at end of file
diff --git a/screenshots/Artify ERD.png b/screenshots/Artify ERD.png
new file mode 100644
index 0000000000000000000000000000000000000000..376ab9b847ea2dd2ac3facbffe53b8b69d7f94a0
Binary files /dev/null and b/screenshots/Artify ERD.png differ
diff --git a/src/Controllers/ArtworksController.cs b/src/Controllers/ArtworksController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2419cf2427966298d3ad7b1928b31366d10b5edc
--- /dev/null
+++ b/src/Controllers/ArtworksController.cs
@@ -0,0 +1,129 @@
+using System.Security.Claims;
+using Backend_Teamwork.src.DTO;
+using Backend_Teamwork.src.Entities;
+using Backend_Teamwork.src.Services.artwork;
+using Backend_Teamwork.src.Utils;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using static Backend_Teamwork.src.DTO.ArtworkDTO;
+
+namespace Backend_Teamwork.src.Controllers
+{
+ [ApiController]
+ [Route("api/v1/[controller]")]
+ public class ArtworksController : ControllerBase
+ {
+ private readonly IArtworkService _artworkService;
+
+ // Constructor
+ public ArtworksController(IArtworkService service)
+ {
+ _artworkService = service;
+ }
+
+ // Create
+ // End-Point: api/v1/artworks
+ [HttpPost]
+ [Authorize(Roles = "Artist")]
+ public async Task> CreateOne(
+ [FromBody] ArtworkCreateDto createDto
+ )
+ {
+ // extract user information
+ var authenticateClaims = HttpContext.User;
+ // get user id from claim
+ var userId = authenticateClaims
+ .FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!
+ .Value;
+ // string => guid
+ var userGuid = new Guid(userId);
+
+ var createdArtwork = await _artworkService.CreateOneAsync(userGuid, createDto);
+ return Ok(createdArtwork);
+ }
+
+ // Get all artworks
+ // End-Point: api/v1/artworks
+ [HttpGet]
+ public async Task>>> GetAll(
+ [FromQuery] PaginationOptions paginationOptions
+ )
+ {
+ var artworkList = await _artworkService.GetAllAsync(paginationOptions);
+ var totalCount = await _artworkService.GetArtworkCountAsync(); // Get the total count
+ var response = new PaginatedResponse>
+ {
+ Items = artworkList,
+ TotalCount = totalCount
+ };
+ return Ok(response);
+ }
+
+ // Get by artwork id
+ // End-Point: api/v1/artworks/{id}
+ [HttpGet("{id}")]
+ public async Task> GetById([FromRoute] Guid id)
+ {
+ var artwork = await _artworkService.GetByIdAsync(id);
+ return Ok(artwork);
+ }
+
+ // Get by artist Id
+ // End-Point: api/v1/artworks/artist/{id}
+ [HttpGet("artist/{artistId}")]
+ public async Task>> GetByArtistId(
+ [FromRoute] Guid artistId
+ )
+ {
+ var artwork = await _artworkService.GetByArtistIdAsync(artistId);
+ return Ok(artwork);
+ }
+
+ // Get count of all artworks
+ // End-Point: api/v1/artworks/count
+ [HttpGet("count")]
+ public async Task> GetArtworkCount()
+ {
+ var count = await _artworkService.GetArtworkCountAsync();
+ return Ok(count);
+ }
+
+ // // Get count of artworks by artistId
+ // // End-Point: api/v1/artworks/artist/{artistId}/count
+ // [HttpGet("artist/{artistId}/count")]
+ // public async Task> GetArtworkCountByArtist([FromRoute] Guid artistId)
+ // {
+ // var count = await _artworkService.GetArtworkCountByArtistAsync(artistId);
+ // return Ok(count);
+ // }
+
+ // Update
+ // End-Point: api/v1/artworks/{id}
+ [HttpPut("{id}")]
+ [Authorize(Roles = "Admin,Artist")]
+ public async Task UpdateOne(
+ [FromRoute] Guid id,
+ [FromBody] ArtworkUpdateDTO updateDTO
+ )
+ {
+ var artwork = await _artworkService.UpdateOneAsync(id, updateDTO);
+ return Ok(artwork);
+ }
+
+ // Delete
+ // End-Point: api/v1/artworks/{id}
+ [HttpDelete("{id}")]
+ [Authorize(Roles = "Admin,Artist")]
+ public async Task DeleteOne([FromRoute] Guid id)
+ {
+ await _artworkService.DeleteOneAsync(id);
+ return NoContent();
+ }
+ }
+
+ public class PaginatedResponse
+ {
+ public T Items { get; set; }
+ public int TotalCount { get; set; }
+ }
+}
diff --git a/src/Controllers/BookingsController.cs b/src/Controllers/BookingsController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3f31777eaf9f50c02dd78b980cd24e1abe862f87
--- /dev/null
+++ b/src/Controllers/BookingsController.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Backend_Teamwork.src.Entities;
+using Backend_Teamwork.src.Services.booking;
+using Backend_Teamwork.src.Utils;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using static Backend_Teamwork.src.DTO.BookingDTO;
+
+namespace Backend_Teamwork.src.Controllers
+{
+ [ApiController]
+ [Route("/api/v1/[controller]")]
+ public class BookingsController : ControllerBase
+ {
+ private readonly IBookingService _bookingService;
+
+ public BookingsController(IBookingService bookingService)
+ {
+ _bookingService = bookingService;
+ }
+
+ [HttpGet]
+ [Authorize(Roles = "Admin")]
+ public async Task>> GetBookings()
+ {
+ var bookings = await _bookingService.GetAllAsync();
+ return Ok(bookings);
+ }
+
+ [HttpGet("{id}")]
+ [Authorize(Roles = "Admin,Customer")]
+ public async Task> GetBookingById([FromRoute] Guid id)
+ {
+ var authClaims = HttpContext.User;
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
+ var userRole = authClaims.FindFirst(c => c.Type == ClaimTypes.Role)!.Value;
+ var convertedUserId = new Guid(userId);
+ var booking = await _bookingService.GetByIdAsync(id, convertedUserId, userRole);
+ return Ok(booking);
+ }
+
+ [HttpGet("search/{userId:guid}")]
+ [Authorize(Roles = "Admin")]
+ public async Task>> GetBookingsByUserId(
+ [FromRoute] Guid userId
+ )
+ {
+ var bookings = await _bookingService.GetByUserIdAsync(userId);
+ return Ok(bookings);
+ }
+
+ [HttpGet("my-bookings")]
+ [Authorize(Roles = "Customer")]
+ public async Task>> GetBookingsByUserId()
+ {
+ var authClaims = HttpContext.User;
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
+ var convertedUserId = new Guid(userId);
+ var bookings = await _bookingService.GetByUserIdAsync(convertedUserId);
+ return Ok(bookings);
+ }
+
+ [HttpGet("search/{status:alpha}")]
+ [Authorize(Roles = "Admin")]
+ public async Task>> GetBookingsByStatus(
+ [FromRoute] string status
+ )
+ {
+ var bookings = await _bookingService.GetByStatusAsync(status);
+ return Ok(bookings);
+ }
+
+ [HttpGet("my-bookings/search/{status}")]
+ [Authorize(Roles = "Customer")]
+ public async Task>> GetBookingsByUserIdAndStatus(
+ [FromRoute] string status
+ )
+ {
+ var authClaims = HttpContext.User;
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
+ var convertedUserId = new Guid(userId);
+ var bookings = await _bookingService.GetByUserIdAndStatusAsync(convertedUserId, status);
+ return Ok(bookings);
+ }
+
+ [HttpGet("page")]
+ [Authorize(Roles = "Admin")]
+ public async Task>> GetBookingsWithPagination(
+ [FromQuery] PaginationOptions paginationOptions
+ )
+ {
+ var bookings = await _bookingService.GetWithPaginationAsync(paginationOptions);
+ return Ok(bookings);
+ }
+
+ [HttpGet("my-bookings/page")]
+ [Authorize(Roles = "Customer")]
+ public async Task>> GetBookingsByUserIdWithPagination(
+ [FromQuery] PaginationOptions paginationOptions
+ )
+ {
+ var authClaims = HttpContext.User;
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
+ paginationOptions.Search=userId;
+ var bookings = await _bookingService.GetByUserIdWithPaginationAsync(paginationOptions);
+ return Ok(bookings);
+ }
+
+ [HttpPost]
+ [Authorize(Roles = "Customer")]
+ public async Task> CreateBooking(
+ [FromBody] BookingCreateDto bookingDTO
+ )
+ {
+ var authClaims = HttpContext.User;
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
+ var convertedUserId = new Guid(userId);
+ var booking = await _bookingService.CreateAsync(bookingDTO, convertedUserId);
+ return CreatedAtAction(nameof(CreateBooking), new { id = booking.Id }, booking);
+ }
+
+ [HttpPut("confirm/{id}")]
+ [Authorize(Roles = "Admin")]
+ public async Task> ConfirmBooking([FromRoute] Guid id)
+ {
+ var booking = await _bookingService.ConfirmAsync(id);
+ return Ok(booking);
+ }
+
+ [HttpPut("reject/{workshopId}")]
+ [Authorize(Roles = "Admin")]
+ public async Task>> RejectBooking(
+ [FromRoute] Guid workshopId
+ )
+ {
+ var bookings = await _bookingService.RejectAsync(workshopId);
+ return Ok(bookings);
+ }
+
+ [HttpPut("my-bookings/cancel/{id}")]
+ [Authorize(Roles = "Customer")]
+ public async Task> CancelBooking([FromRoute] Guid id)
+ {
+ var authClaims = HttpContext.User;
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
+ var convertedUserId = new Guid(userId);
+ var booking = await _bookingService.CancelAsync(id, convertedUserId);
+ return Ok(booking);
+ }
+ }
+}
diff --git a/src/Controllers/CategoriesController.cs b/src/Controllers/CategoriesController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1210a13aca3aec4e24ddea8bc4606885695352a9
--- /dev/null
+++ b/src/Controllers/CategoriesController.cs
@@ -0,0 +1,90 @@
+using System.Text.Json;
+using Backend_Teamwork.src.Entities;
+using Backend_Teamwork.src.Services.category;
+using Backend_Teamwork.src.Utils;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using static Backend_Teamwork.src.DTO.CategoryDTO;
+
+namespace Backend_Teamwork.src.Controllers
+{
+ [ApiController]
+ [Route("/api/v1/[controller]")]
+ public class CategoriesController : ControllerBase
+ {
+ private readonly ICategoryService _categoryService;
+
+ public CategoriesController(ICategoryService categoryService)
+ {
+ _categoryService = categoryService;
+ }
+
+ [HttpGet]
+ public async Task>> GetCategories()
+ {
+ var categories = await _categoryService.GetAllAsync();
+ return Ok(categories);
+ }
+
+ [HttpGet("{id}")]
+ public async Task> GetCategoryById([FromRoute] Guid id)
+ {
+ var category = await _categoryService.GetByIdAsync(id);
+ return Ok(category);
+ }
+
+ [HttpGet("search/{name}")]
+ public async Task> GetCategoryByName([FromRoute] string name)
+ {
+ var category = await _categoryService.GetByNameAsync(name);
+ return Ok(category);
+ }
+
+ [HttpGet("page")]
+ public async Task>> GetCategoriesWithPaginationAsync(
+ [FromQuery] PaginationOptions paginationOptions
+ )
+ {
+ var categories = await _categoryService.GetWithPaginationAsync(paginationOptions);
+ return Ok(categories);
+ }
+
+ [HttpGet("sort")]
+ public async Task>> SortCategoriesByName()
+ {
+ var categories = await _categoryService.SortByNameAsync();
+ return Ok(categories);
+ }
+
+ [HttpPost]
+ [Authorize(Roles = "Admin")]
+ public async Task> CreateCategory(
+ [FromBody] CategoryCreateDto categoryDTO
+ )
+ {
+ var category = await _categoryService.CreateAsync(categoryDTO);
+ return CreatedAtAction(nameof(CreateCategory), new { id = category.Id }, category);
+ }
+
+ [HttpPut("{id}")]
+ [Authorize(Roles = "Admin")]
+ public async Task> UpdateCategory(
+ [FromRoute] Guid id,
+ [FromBody] CategoryUpdateDto categoryDTO
+ )
+ {
+ var category = await _categoryService.UpdateAsync(id, categoryDTO);
+ return Ok(category);
+ }
+
+ [HttpDelete("{id}")]
+ [Authorize(Roles = "Admin")]
+ public async Task DeleteCategory([FromRoute] Guid id)
+ {
+ await _categoryService.DeleteAsync(id);
+ return NoContent();
+ }
+
+
+ }
+}
diff --git a/src/Controllers/OrdersController.cs b/src/Controllers/OrdersController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..08755a3fdd95b099606809fd08986d5495f9b3f9
--- /dev/null
+++ b/src/Controllers/OrdersController.cs
@@ -0,0 +1,126 @@
+using System.Security.Claims;
+using Backend_Teamwork.src.Entities;
+using Backend_Teamwork.src.Services.order;
+using Backend_Teamwork.src.Utils;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using static Backend_Teamwork.src.DTO.OrderDTO;
+
+namespace Backend_Teamwork.src.Controllers
+{
+ [Route("api/v1/[controller]")]
+ [ApiController]
+ public class OrdersController : ControllerBase
+ {
+ private readonly IOrderService _orderService;
+
+ public OrdersController(IOrderService service)
+ {
+ _orderService = service;
+ }
+
+ // GET: api/v1/orders
+ [HttpGet]
+ [Authorize(Roles = "Admin")] // Accessible by Admin
+ public async Task>> GetOrders()
+ {
+ var orders = await _orderService.GetAllAsync();
+ return Ok(orders);
+ }
+
+ // GET: api/v1/orders
+ [HttpGet("my-orders")]
+ [Authorize(Roles = "Customer")]
+ public async Task>> GetMyOrders()
+ {
+ var authClaims = HttpContext.User;
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
+ var convertedUserId = new Guid(userId);
+ var orders = await _orderService.GetAllAsync(convertedUserId);
+ return Ok(orders);
+ }
+
+ // GET: api/v1/orders/{id}
+ [HttpGet("{id}")]
+ [Authorize(Roles = "Admin")] // Accessible by Admin
+ public async Task> GetOrderById([FromRoute] Guid id)
+ {
+ var order = await _orderService.GetByIdAsync(id);
+ return Ok(order);
+ }
+
+ // GET: api/v1/orders/{id}
+ [HttpGet("my-orders/{id}")]
+ [Authorize(Roles = "Customer")]
+ public async Task> GetMyOrderById([FromRoute] Guid id)
+ {
+ var authClaims = HttpContext.User;
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
+ var convertedUserId = new Guid(userId);
+ var order = await _orderService.GetByIdAsync(id, convertedUserId);
+ return Ok(order);
+ }
+
+ // POST: api/v1/orders
+ [HttpPost("add")]
+ [Authorize(Roles = "Customer")]
+ public async Task> AddOrder([FromBody] OrderCreateDto createDto)
+ {
+ // extract user information
+ var authenticateClaims = HttpContext.User;
+ var userId = authenticateClaims
+ .FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!
+ .Value;
+ var userGuid = new Guid(userId);
+
+ var orderCreated = await _orderService.CreateOneAsync(userGuid, createDto);
+
+ return CreatedAtAction(
+ nameof(GetOrderById),
+ new { id = orderCreated.Id },
+ orderCreated
+ );
+ }
+
+ // PUT: api/v1/orders/{id}
+ [HttpPut("{id}")]
+ [Authorize(Roles = "Admin")] // Accessible by Admin
+ public async Task> UpdateOrder(
+ [FromRoute] Guid id,
+ [FromBody] OrderUpdateDto updateDto
+ )
+ {
+ var isUpdated = await _orderService.UpdateOneAsync(id, updateDto);
+ return Ok(isUpdated);
+ }
+
+ // DELETE: api/v1/orders/{id}
+ [HttpDelete("{id}")]
+ [Authorize(Roles = "Admin")] // Accessible by Admin
+ public async Task> DeleteOrder(Guid id)
+ {
+ await _orderService.DeleteOneAsync(id);
+ return NoContent();
+ }
+
+ // Extra Features
+ // GET: api/v1/users/page
+ [HttpGet("pagination")]
+ [Authorize(Roles = "Admin")] // Accessible by Admin
+ public async Task>> GetOrdersByPage(
+ [FromQuery] PaginationOptions paginationOptions
+ )
+ {
+ var orders = await _orderService.GetOrdersByPage(paginationOptions);
+ return Ok(orders);
+ }
+
+ [HttpGet("sort-by-date")]
+ [Authorize(Roles = "Admin")]
+ public async Task>> SortOrdersByDate()
+ {
+ var orders = await _orderService.SortOrdersByDate();
+ return Ok(orders);
+ }
+ }
+}
diff --git a/src/Controllers/PaymentsController.cs b/src/Controllers/PaymentsController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f24b9ec6055956d65cad33f81f8bd89868bb4f58
--- /dev/null
+++ b/src/Controllers/PaymentsController.cs
@@ -0,0 +1,60 @@
+using Backend_Teamwork.src.Entities;
+using Backend_Teamwork.src.Services.payment;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using static Backend_Teamwork.src.DTO.PaymentDTO;
+
+namespace Backend_Teamwork.src.Controllers
+{
+ [ApiController]
+ [Route("api/v1/[controller]")]
+ public class PaymentController : ControllerBase
+ {
+ private readonly IPaymentService _paymentService;
+
+ public PaymentController(IPaymentService paymentService)
+ {
+ _paymentService = paymentService;
+ }
+
+ [HttpGet]
+ [Authorize(Roles = "Admin")]
+ public async Task>> GetPayments()
+ {
+ var payments = await _paymentService.GetAllAsync();
+ return Ok(payments);
+ }
+
+ [HttpGet("{id}")]
+ [Authorize(Roles = "Admin")]
+ public async Task> GetPaymentById(Guid id)
+ {
+ var payment = await _paymentService.GetByIdAsync(id);
+ return Ok(payment);
+ }
+
+ [HttpPost]
+ [Authorize]
+ public async Task> CreatePayment(PaymentCreateDTO newPayment)
+ {
+ var payment = await _paymentService.CreateOneAsync(newPayment);
+ return CreatedAtAction(nameof(GetPaymentById), new { id = payment.Id }, payment);
+ }
+
+ [HttpDelete("{id}")]
+ [Authorize(Roles = "Admin")]
+ public async Task DeletePayment(Guid id)
+ {
+ await _paymentService.DeleteOneAsync(id);
+ return NoContent();
+ }
+
+ [HttpPut("{id}")]
+ [Authorize(Roles = "Admin")]
+ public ActionResult UpdatePayment(Guid id, PaymentUpdateDTO updatedPayment)
+ {
+ var payment = _paymentService.UpdateOneAsync(id,updatedPayment);
+ return Ok(payment);
+ }
+ }
+}
diff --git a/src/Controllers/UsersController.cs b/src/Controllers/UsersController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..cbe05753241420a3ce89fe45f224732fd85a8d8f
--- /dev/null
+++ b/src/Controllers/UsersController.cs
@@ -0,0 +1,133 @@
+using System.Security.Claims;
+using Backend_Teamwork.src.Services.user;
+using Backend_Teamwork.src.Utils;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using static Backend_Teamwork.src.DTO.UserDTO;
+using static Backend_Teamwork.src.Entities.User;
+
+namespace Backend_Teamwork.src.Controllers
+{
+ [ApiController]
+ [Route("api/v1/[controller]")]
+ public class UsersController : ControllerBase
+ {
+ private readonly IUserService _userService;
+
+ // DI
+ public UsersController(IUserService service)
+ {
+ _userService = service;
+ }
+
+ // GET: api/v1/users
+ [HttpGet]
+ // [Authorize(Roles = "Admin")] // Only Admin
+ public async Task>> GetUsers(
+ [FromQuery] PaginationOptions paginationOptions
+ )
+ {
+ var users = await _userService.GetAllAsync(paginationOptions);
+ return Ok(users);
+ }
+
+ [HttpGet("{id:guid}")]
+ // [Authorize(Roles = "Admin")] // Only Admin
+ public async Task> GetUserById([FromRoute] Guid id)
+ {
+ var user = await _userService.GetByIdAsync(id);
+ return Ok(user);
+ }
+
+ [HttpGet("profile")]
+ // [Authorize]
+ public async Task> GetInformationById()
+ {
+ // Get the user ID from the token claims
+ var authClaims = HttpContext.User;
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
+ var convertedUserId = new Guid(userId);
+ var user = await _userService.GetByIdAsync(convertedUserId);
+ return Ok(user);
+ }
+
+ // GET: api/v1/users/email
+ [HttpGet("email/{email}")]
+ // [Authorize(Roles = "Admin")] // Only Admin
+ public async Task> GetByEmail([FromRoute] string email)
+ {
+ var user = await _userService.GetByEmailAsync(email);
+ return Ok(user);
+ }
+
+ // POST: api/v1/users
+ [HttpPost]
+ public async Task> SignUp([FromBody] UserCreateDto createDto)
+ {
+ var UserCreated = await _userService.CreateOneAsync(createDto);
+ return CreatedAtAction(nameof(GetUserById), new { id = UserCreated.Id }, UserCreated);
+ }
+
+ // POST: api/v1/users/create-admin
+ [HttpPost("create-admin")]
+ // [Authorize(Roles = "Admin")] // Only Admin
+ public async Task> CreateAdmin([FromBody] UserCreateDto createDto)
+ {
+ createDto.Role = UserRole.Admin; // Set role as 'Admin'
+ var adminCreated = await _userService.CreateOneAsync(createDto);
+ return CreatedAtAction(nameof(GetUserById), new { id = adminCreated.Id }, adminCreated);
+ }
+
+ // POST: api/v1/users/signin
+ [HttpPost("signin")]
+ public async Task> SignIn([FromBody] UserSigninDto signinDto)
+ {
+ var token = await _userService.SignInAsync(signinDto);
+ return Ok(token);
+ }
+
+ [HttpPut("{id:guid}")]
+ // [Authorize(Roles = "Admin")] // Only Admin
+ public async Task> UpdateUser(
+ [FromRoute] Guid id,
+ [FromBody] UserUpdateDto updateDto
+ )
+ {
+ await _userService.UpdateOneAsync(id, updateDto);
+ return NoContent();
+ } // should ask my teammates
+
+ [HttpPut("profile")]
+ // [Authorize]
+ public async Task> UpdateProfileInformation(
+ [FromBody] UserUpdateDto updateDto
+ )
+ {
+ // Get the user ID from the token claims
+ var authClaims = HttpContext.User;
+ var userId = authClaims.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!.Value;
+ var convertedUserId = new Guid(userId);
+ await _userService.UpdateOneAsync(convertedUserId, updateDto);
+ return NoContent();
+ }
+
+ // DELETE: api/v1/users/{id}
+ [HttpDelete("{id:guid}")]
+ // [Authorize(Roles = "Admin")] // Only Admin
+ public async Task> DeleteUser([FromRoute] Guid id)
+ {
+ await _userService.DeleteOneAsync(id);
+ return NoContent();
+ }
+
+ // Extra Features
+ // GET: api/v1/users/count
+ [HttpGet("count")]
+ // [Authorize(Roles = "Admin")] // Only Admin
+ public async Task> GetTotalUsersCount()
+ {
+ var count = await _userService.GetTotalUsersCountAsync();
+ return Ok(count);
+ }
+ }
+}
diff --git a/src/Controllers/WorkshopsController.cs b/src/Controllers/WorkshopsController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f4761af8c7e45f78a70b0f6425e13febf99e7ef3
--- /dev/null
+++ b/src/Controllers/WorkshopsController.cs
@@ -0,0 +1,86 @@
+using System.Security.Claims;
+using Backend_Teamwork.src.Services.workshop;
+using Backend_Teamwork.src.Utils;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using static Backend_Teamwork.src.DTO.WorkshopDTO;
+
+namespace sda_3_online_Backend_Teamwork.src.Controllers
+{
+ [ApiController]
+ [Route("api/v1/[controller]")]
+ public class WorkshopsController : ControllerBase
+ {
+ private readonly IWorkshopService _workshopService;
+
+ public WorkshopsController(IWorkshopService service)
+ {
+ _workshopService = service;
+ }
+
+ [HttpGet]
+ public async Task>> GetWorkshop()
+ {
+ var workshops = await _workshopService.GetAllAsync();
+ return Ok(workshops);
+ }
+
+ [HttpGet("{id}")]
+ public async Task> GetWorkshopById([FromRoute] Guid id)
+ {
+ var workshop = await _workshopService.GetByIdAsync(id);
+ return Ok(workshop);
+ }
+
+ [HttpGet("page")]
+ public async Task> GetWorkShopByPage(
+ [FromQuery] PaginationOptions paginationOptions
+ )
+ {
+ var workshops = await _workshopService.GetAllAsync(paginationOptions);
+ return Ok(workshops);
+ }
+
+ [HttpPost]
+ [Authorize(Roles = "Admin,Artist")]
+ public async Task> CreateWorkshop(
+ [FromBody] WorkshopCreateDTO createDto
+ )
+ {
+ // extract user information
+ var authenticateClaims = HttpContext.User;
+ // get user id from claim
+ var userId = authenticateClaims
+ .FindFirst(c => c.Type == ClaimTypes.NameIdentifier)!
+ .Value;
+ // string => guid
+ var userGuid = new Guid(userId);
+
+ var workshopCreated = await _workshopService.CreateOneAsync(userGuid, createDto);
+ return CreatedAtAction(
+ nameof(GetWorkshopById),
+ new { id = workshopCreated.Id },
+ workshopCreated
+ );
+ }
+
+ [HttpDelete("{id}")]
+ [Authorize(Roles = "Admin,Artist")]
+ public async Task> DeleteWorkshop([FromRoute] Guid id)
+ {
+ var isDeleted = await _workshopService.DeleteOneAsync(id);
+ return NoContent();
+ }
+
+ [HttpPut("{id}")]
+ [Authorize(Roles = "Admin,Artist")]
+ public async Task> UpdateWorkshop(
+ Guid id,
+ [FromBody] WorkshopUpdateDTO updateDto
+ )
+ {
+ var updateWorkshop = await _workshopService.UpdateOneAsync(id, updateDto);
+ return Ok(updateWorkshop);
+ }
+ }
+}
diff --git a/src/DTO/ArtworkDTO.cs b/src/DTO/ArtworkDTO.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b12b15ef165ac940a7ffca09e436284f4b0b9781
--- /dev/null
+++ b/src/DTO/ArtworkDTO.cs
@@ -0,0 +1,79 @@
+using System.ComponentModel.DataAnnotations;
+using Backend_Teamwork.src.Entities;
+using static Backend_Teamwork.src.DTO.UserDTO;
+
+namespace Backend_Teamwork.src.DTO
+{
+ public class ArtworkDTO
+ {
+ // create Artwork
+ public class ArtworkCreateDto
+ {
+ [
+ Required(ErrorMessage = "Title shouldn't be null"),
+ MinLength(6, ErrorMessage = "Title should be at at least 6 characters"),
+ MaxLength(30, ErrorMessage = "Title shouldn't be more than 30 characters")
+ ]
+ public string Title { get; set; }
+
+ [
+ Required(ErrorMessage = "Description shouldn't be null"),
+ MinLength(30, ErrorMessage = "Description should be at at least 30 characters"),
+ MaxLength(200, ErrorMessage = "Description shouldn't be more than 200 characters")
+ ]
+ public string Description { get; set; }
+
+ [Range(1, int.MaxValue, ErrorMessage = "Quantity should be greater than zero.")]
+ public int Quantity { get; set; }
+
+ [Range(1.0, double.MaxValue, ErrorMessage = "Price should be greater than zero.")]
+ public decimal Price { get; set; }
+ public DateTime? CreatedAt { get; set; } = DateTime.Now;
+
+ [Required(ErrorMessage = "Category Id shouldn't be null")]
+ public Guid CategoryId { get; set; }
+ }
+
+ // read data (get data)
+ public class ArtworkReadDto
+ {
+ public Guid Id { get; set; }
+ public string Title { get; set; }
+ public string Description { get; set; }
+ public int Quantity { get; set; }
+ public decimal Price { get; set; }
+ public DateTime CreatedAt { get; set; }
+ public Category Category { get; set; }
+ public UserReadDto User { get; set; }
+ }
+
+ // update
+ public class ArtworkUpdateDTO
+ {
+ [
+ Required(ErrorMessage = "Title shouldn't be null"),
+ MinLength(6, ErrorMessage = "Title should be at at least 6 characters"),
+ MaxLength(30, ErrorMessage = "Title shouldn't be more than 30 characters")
+ ]
+ public string Title { get; set; }
+
+ [
+ Required(ErrorMessage = "Description shouldn't be null"),
+ MinLength(30, ErrorMessage = "Description should be at at least 30 characters"),
+ MaxLength(200, ErrorMessage = "Description shouldn't be more than 200 characters")
+ ]
+ public string Description { get; set; }
+
+ [Range(1, int.MaxValue, ErrorMessage = "Quantity should be greater than zero.")]
+ public int Quantity { get; set; }
+
+ [Range(1.0, double.MaxValue, ErrorMessage = "Price should be greater than zero.")]
+ public decimal Price { get; set; }
+ }
+ public class ArtworkResponseDto
+ {
+ public List Artworks { get; set; }
+ public int TotalCount { get; set; }
+ }
+ }
+}
diff --git a/src/DTO/BookingDTO.cs b/src/DTO/BookingDTO.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a80934b75ac4b740930aec04296daab843d13ef7
--- /dev/null
+++ b/src/DTO/BookingDTO.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel.DataAnnotations;
+using Backend_Teamwork.src.Entities;
+
+namespace Backend_Teamwork.src.DTO
+{
+ public class BookingDTO
+ {
+ public class BookingCreateDto
+ {
+ [Required(ErrorMessage = "Workshop Id shouldn't be null"),]
+ public Guid WorkshopId { get; set; }
+ public DateTime? CreatedAt { get; set; } = DateTime.Now;
+ }
+
+ public class BookingReadDto
+ {
+ public Guid Id { get; set; }
+ public Status Status { get; set; }
+ public DateTime CreatedAt { get; set; }
+ public Workshop Workshop { get; set; }
+ public User User { get; set; }
+ }
+ }
+}
diff --git a/src/DTO/CategoryDTO.cs b/src/DTO/CategoryDTO.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ab872b02aa3fb051aac8177374764bede10c4175
--- /dev/null
+++ b/src/DTO/CategoryDTO.cs
@@ -0,0 +1,33 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Backend_Teamwork.src.DTO
+{
+ public class CategoryDTO
+ {
+ public class CategoryCreateDto
+ {
+ [
+ Required(ErrorMessage = "Name shouldn't be null"),
+ MinLength(2, ErrorMessage = "Name should be at at least 2 characters"),
+ MaxLength(10, ErrorMessage = "Name shouldn't be more than 10 characters")
+ ]
+ public string Name { get; set; }
+ }
+
+ public class CategoryUpdateDto
+ {
+ [
+ Required(ErrorMessage = "Name shouldn't be null"),
+ MinLength(2, ErrorMessage = "Name should be at at least 2 characters"),
+ MaxLength(10, ErrorMessage = "Name shouldn't be more than 10 characters")
+ ]
+ public string Name { get; set; }
+ }
+
+ public class CategoryReadDto
+ {
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+ }
+ }
+}
diff --git a/src/DTO/OrderDTO.cs b/src/DTO/OrderDTO.cs
new file mode 100644
index 0000000000000000000000000000000000000000..072f86328421237f6a4ec44e5a8cfaf93b5310c9
--- /dev/null
+++ b/src/DTO/OrderDTO.cs
@@ -0,0 +1,53 @@
+using System.ComponentModel.DataAnnotations;
+using static Backend_Teamwork.src.DTO.OrderDetailDTO;
+using static Backend_Teamwork.src.DTO.UserDTO;
+
+namespace Backend_Teamwork.src.DTO
+{
+ public class OrderDTO
+ {
+ // DTO for creating a new order
+ public class OrderCreateDto
+ {
+ [
+ Required(ErrorMessage = "Address shouldn't be null"),
+ MinLength(10, ErrorMessage = "Address should be at at least 10 characters"),
+ MaxLength(30, ErrorMessage = "Address shouldn't be more than 30 characters")
+ ]
+ public string ShippingAddress { get; set; }
+ public DateTime? CreatedAt { get; set; } = DateTime.Now;
+
+ [Required(ErrorMessage = "Order details shouldn't be null")]
+ public List OrderDetails { get; set; }
+ }
+
+ // DTO for reading order data
+ public class OrderReadDto
+ {
+ public Guid Id { get; set; }
+ public decimal TotalAmount { get; set; }
+ public string? ShippingAddress { get; set; }
+ public DateTime? CreatedAt { get; set; }
+ public UserReadDto User { get; set; }
+ public List OrderDetails { get; set; }
+ }
+
+ // DTO for updating an existing order
+ public class OrderUpdateDto
+ {
+ [Range(
+ 1.0,
+ double.MaxValue,
+ ErrorMessage = "Total amount should be greater than zero."
+ )]
+ public decimal TotalAmount { get; set; }
+
+ [
+ Required(ErrorMessage = "Address shouldn't be null"),
+ MinLength(10, ErrorMessage = "Address should be at at least 10 characters"),
+ MaxLength(30, ErrorMessage = "Address shouldn't be more than 30 characters")
+ ]
+ public string? ShippingAddress { get; set; }
+ }
+ }
+}
diff --git a/src/DTO/OrderDetailDTO.cs b/src/DTO/OrderDetailDTO.cs
new file mode 100644
index 0000000000000000000000000000000000000000..67a95451d8ffa9f05691fe99b5d8c4ac5b062c9d
--- /dev/null
+++ b/src/DTO/OrderDetailDTO.cs
@@ -0,0 +1,30 @@
+using System.ComponentModel.DataAnnotations;
+using static Backend_Teamwork.src.DTO.ArtworkDTO;
+
+namespace Backend_Teamwork.src.DTO
+{
+ public class OrderDetailDTO
+ {
+ public class OrderDetailCreateDto
+ {
+ [Required(ErrorMessage = "Artwork Id shouldn't be null")]
+ public Guid ArtworkId { get; set; }
+
+ [Range(1, int.MaxValue, ErrorMessage = "Quantity should be greater than zero.")]
+ public int Quantity { get; set; }
+ }
+
+ public class OrderDetailReadDto
+ {
+ public Guid Id { get; set; }
+ public int Quantity { get; set; }
+ public ArtworkReadDto? Artwork { get; set; }
+ }
+
+ public class OrderDetailUpdateDto
+ {
+ [Range(1, int.MaxValue, ErrorMessage = "Quantity should be greater than zero.")]
+ public int Quantity { get; set; }
+ }
+ }
+}
diff --git a/src/DTO/PaymentDTO.cs b/src/DTO/PaymentDTO.cs
new file mode 100644
index 0000000000000000000000000000000000000000..20917cbfa759d474fdc7b6c5d27ec9bab1a6c499
--- /dev/null
+++ b/src/DTO/PaymentDTO.cs
@@ -0,0 +1,47 @@
+using System.ComponentModel.DataAnnotations;
+using Backend_Teamwork.src.Entities;
+
+namespace Backend_Teamwork.src.DTO
+{
+ public class PaymentDTO
+ {
+ public class PaymentCreateDTO
+ {
+ [
+ Required(ErrorMessage = "Payment method shouldn't be null"),
+ MinLength(10, ErrorMessage = "Payment method should be at at least 10 characters"),
+ MaxLength(30, ErrorMessage = "Payment method shouldn't be more than 30 characters")
+ ]
+ public string PaymentMethod { get; set; }
+
+ [Range(1.0, double.MaxValue, ErrorMessage = "Price should be greater than zero.")]
+ public decimal Amount { get; set; }
+ public DateTime? CreatedAt { get; set; } = DateTime.Now;
+ public Guid? OrderId { get; set; } = Guid.Empty;
+ public Guid? BookingId { get; set; } = Guid.Empty;
+ }
+
+ public class PaymentReadDTO
+ {
+ public Guid Id { get; set; }
+ public string PaymentMethod { get; set; }
+ public decimal Amount { get; set; }
+ public DateTime CreatedAt { get; set; }
+ public Order? Order { get; set; }
+ public Booking? Booking { get; set; }
+ }
+
+ public class PaymentUpdateDTO
+ {
+ [
+ Required(ErrorMessage = "Payment method shouldn't be null"),
+ MinLength(10, ErrorMessage = "Payment method should be at at least 10 characters"),
+ MaxLength(30, ErrorMessage = "Payment method shouldn't be more than 30 characters")
+ ]
+ public string PaymentMethod { get; set; }
+
+ [Range(1.0, double.MaxValue, ErrorMessage = "Amount should be greater than zero.")]
+ public decimal Amount { get; set; }
+ }
+ }
+}
diff --git a/src/DTO/UserDTO.cs b/src/DTO/UserDTO.cs
new file mode 100644
index 0000000000000000000000000000000000000000..92885ab89e525dfc93c9e7453d5807abc33855c6
--- /dev/null
+++ b/src/DTO/UserDTO.cs
@@ -0,0 +1,98 @@
+using System.ComponentModel.DataAnnotations;
+using Backend_Teamwork.src.Utils;
+using static Backend_Teamwork.src.Entities.User;
+
+namespace Backend_Teamwork.src.DTO
+{
+ public class UserDTO
+ {
+ // DTO for creating a new User (including Artist)
+ public class UserCreateDto
+ {
+ [
+ Required(ErrorMessage = "Name shouldn't be null"),
+ MinLength(2, ErrorMessage = "Name should be at at least 2 characters"),
+ MaxLength(10, ErrorMessage = "Name shouldn't be more than 10 characters")
+ ]
+ public string Name { get; set; }
+
+ [
+ Required(ErrorMessage = "Phone number shouldn't be null"),
+ RegularExpression(
+ @"^\+966[5][0-9]{8}$",
+ ErrorMessage = "Phone number should be a valid Saudi phone number"
+ )
+ ]
+ public string PhoneNumber { get; set; }
+
+ [
+ Required(ErrorMessage = "Email shouldn't be null"),
+ EmailAddress(ErrorMessage = "Email should be with right format: @gmail.com")
+ ]
+ public string Email { get; set; }
+
+ [
+ Required(ErrorMessage = "Password shouldn't be null"),
+ MinLength(8, ErrorMessage = "Password should be at at least 8 characters")
+ ]
+ public string Password { get; set; }
+
+ [Required(ErrorMessage = "Role shouldn't be null")]
+ public UserRole Role { get; set; } = UserRole.Customer; // Default to Customer
+
+ // Artist-specific properties (optional)
+ public string? Description { get; set; } // Nullable, only for Artists
+ }
+
+ public class UserSigninDto
+ {
+ [
+ Required(ErrorMessage = "Email shouldn't be null"),
+ EmailAddress(ErrorMessage = "Email should be with right format: @gmail.com")
+ ]
+ public string Email { get; set; }
+
+ [Required(ErrorMessage = "Password shouldn't be null")]
+ public string Password { get; set; }
+ }
+
+ // DTO for reading User data (including Artist)
+ public class UserReadDto
+ {
+ public Guid Id { get; set; }
+ public string? Name { get; set; }
+ public string? PhoneNumber { get; set; }
+ public string? Email { get; set; }
+ public UserRole Role { get; set; }
+
+ // Artist-specific properties (optional)
+ public string? Description { get; set; } // Nullable, only for Artists
+ }
+
+ // DTO for updating an existing User (including Artist)
+ [AtLeastOneRequired(ErrorMessage = "At least one property must be updated.")]
+ public class UserUpdateDto
+ {
+ [
+ MinLength(2, ErrorMessage = "Name should be at at least 2 characters"),
+ MaxLength(10, ErrorMessage = "Name shouldn't be more than 10 characters")
+ ]
+ public string? Name { get; set; }
+
+ [RegularExpression(
+ @"^\+966[5][0-9]{8}$",
+ ErrorMessage = "Phone number should be a valid Saudi phone number"
+ )]
+ public string? PhoneNumber { get; set; }
+
+ [EmailAddress(ErrorMessage = "Email should be with right format: @gmail.com")]
+ public string? Email { get; set; }
+
+ [MinLength(8, ErrorMessage = "Password should be at at least 8 characters")]
+ public string? Password { get; set; }
+
+ [MinLength(2, ErrorMessage = "Description should be at at least 2 characters")]
+ public string? Description { get; set; }
+ }
+ }
+}
diff --git a/src/DTO/WorkshopDTO.cs b/src/DTO/WorkshopDTO.cs
new file mode 100644
index 0000000000000000000000000000000000000000..68afb1776d613e88146d1dfac30125de2f85ddfe
--- /dev/null
+++ b/src/DTO/WorkshopDTO.cs
@@ -0,0 +1,102 @@
+using System.ComponentModel.DataAnnotations;
+using Backend_Teamwork.src.Entities;
+
+namespace Backend_Teamwork.src.DTO
+{
+ public class WorkshopDTO
+ {
+ public class WorkshopCreateDTO
+ {
+ [
+ Required(ErrorMessage = "Name shouldn't be null"),
+ MinLength(10, ErrorMessage = "Name should be at at least 10 characters"),
+ MaxLength(30, ErrorMessage = "Name shouldn't be more than 30 characters")
+ ]
+ public string Name { get; set; }
+
+ [
+ Required(ErrorMessage = "Location shouldn't be null"),
+ MinLength(10, ErrorMessage = "Location should be at at least 10 characters"),
+ MaxLength(30, ErrorMessage = "Location shouldn't be more than 30 characters")
+ ]
+ public string Location { get; set; }
+
+ [
+ Required(ErrorMessage = "Description shouldn't be null"),
+ MinLength(30, ErrorMessage = "Description should be at at least 30 characters"),
+ MaxLength(200, ErrorMessage = "Description shouldn't be more than 200 characters")
+ ]
+ public string Description { get; set; }
+
+ [Required(ErrorMessage = "StartTime shouldn't be null")]
+ public DateTime StartTime { get; set; }
+
+ [Required(ErrorMessage = "EndTime shouldn't be null")]
+ public DateTime EndTime { get; set; }
+
+ [Range(1.0, double.MaxValue, ErrorMessage = "Price should be greater than zero.")]
+ public decimal Price { get; set; }
+
+ [Range(1, int.MaxValue, ErrorMessage = "Capacity should be greater than zero.")]
+ public int Capacity { get; set; }
+
+ [Required(ErrorMessage = "Availability shouldn't be null")]
+ public bool Availability { get; set; }
+ public DateTime? CreatedAt { get; set; } = DateTime.Now;
+ }
+
+ public class WorkshopReadDTO
+ {
+ public Guid Id { get; set; }
+ public string? Name { get; set; }
+ public string? Location { get; set; }
+ public string? Description { get; set; }
+ public DateTime StartTime { get; set; }
+ public DateTime EndTime { get; set; }
+ public decimal Price { get; set; }
+ public int Capacity { get; set; }
+ public bool Availability { get; set; }
+ public DateTime CreatedAt { get; set; }
+ public User User { get; set; }
+ }
+
+ public class WorkshopUpdateDTO
+ {
+ [
+ Required(ErrorMessage = "Name shouldn't be null"),
+ MinLength(10, ErrorMessage = "Name should be at at least 10 characters"),
+ MaxLength(30, ErrorMessage = "Name shouldn't be more than 30 characters")
+ ]
+ public string Name { get; set; }
+
+ [
+ Required(ErrorMessage = "Location shouldn't be null"),
+ MinLength(10, ErrorMessage = "Location should be at at least 10 characters"),
+ MaxLength(30, ErrorMessage = "Location shouldn't be more than 30 characters")
+ ]
+ public string Location { get; set; }
+
+ [
+ Required(ErrorMessage = "Description shouldn't be null"),
+ MinLength(30, ErrorMessage = "Description should be at at least 30 characters"),
+ MaxLength(200, ErrorMessage = "Description shouldn't be more than 200 characters")
+ ]
+ public string Description { get; set; }
+
+ [Required(ErrorMessage = "StartTime shouldn't be null")]
+ public DateTime StartTime { get; set; }
+
+ [Required(ErrorMessage = "EndTime shouldn't be null")]
+ public DateTime EndTime { get; set; }
+
+ [Required(ErrorMessage = "Availability shouldn't be null")]
+ public bool Availability { get; set; }
+
+ [Range(1.0, double.MaxValue, ErrorMessage = "Price should be greater than zero.")]
+ public decimal Price { get; set; }
+
+ [Range(1, int.MaxValue, ErrorMessage = "Capacity should be greater than zero.")]
+ public int Capacity { get; set; }
+ }
+ }
+}
diff --git a/src/Database/DatabaseContext.cs b/src/Database/DatabaseContext.cs
new file mode 100644
index 0000000000000000000000000000000000000000..cb48a498b61b50adf1c762a3dee332f267a6a1a9
--- /dev/null
+++ b/src/Database/DatabaseContext.cs
@@ -0,0 +1,50 @@
+using Backend_Teamwork.src.Entities;
+using Backend_Teamwork.src.Utils;
+using Microsoft.EntityFrameworkCore;
+using static Backend_Teamwork.src.Entities.User;
+
+namespace Backend_Teamwork.src.Database
+{
+ public class DatabaseContext : DbContext
+ {
+ public DbSet Category { get; set; }
+ public DbSet Artwork { get; set; }
+ public DbSet Order { get; set; }
+ public DbSet OrderDetail { get; set; }
+ public DbSet Payment { get; set; }
+ public DbSet Workshop { get; set; }
+ public DbSet User { get; set; }
+ public DbSet Booking { get; set; }
+
+ public DatabaseContext(DbContextOptions option)
+ : base(option) { }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.HasPostgresEnum();
+ modelBuilder.HasPostgresEnum();
+
+ modelBuilder.Entity().HasIndex(x => x.PhoneNumber).IsUnique();
+ modelBuilder.Entity().HasIndex(x => x.Email).IsUnique();
+
+ modelBuilder
+ .Entity()
+ .HasData(
+ new User
+ {
+ Id = Guid.NewGuid(),
+ Name = "Abeer",
+ PhoneNumber = "0563034770",
+ Email = "abeer@gmail.com",
+ Password = PasswordUtils.HashPassword(
+ "123",
+ out string hashedPassword,
+ out byte[] salt
+ ),
+ Role = UserRole.Admin,
+ Salt = salt,
+ }
+ );
+ }
+ }
+}
diff --git a/src/Entities/Artwork.cs b/src/Entities/Artwork.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6cbac32262553ae84d3feda0f4cef2592439a4cb
--- /dev/null
+++ b/src/Entities/Artwork.cs
@@ -0,0 +1,42 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Backend_Teamwork.src.Entities
+{
+ public class Artwork
+ {
+ public Guid Id { get; set; }
+
+ [
+ Required(ErrorMessage = "Title shouldn't be null"),
+ MinLength(6, ErrorMessage = "Title should be at at least 6 characters"),
+ MaxLength(30, ErrorMessage = "Title shouldn't be more than 30 characters")
+ ]
+ public required string Title { get; set; }
+
+ [
+ Required(ErrorMessage = "Description shouldn't be null"),
+ MinLength(30, ErrorMessage = "Description should be at at least 30 characters"),
+ MaxLength(200, ErrorMessage = "Description shouldn't be more than 200 characters")
+ ]
+ public required string Description { get; set; }
+ public required string ImagUrl { get; set; }
+
+ [Range(1, int.MaxValue, ErrorMessage = "Quantity should be greater than zero.")]
+ public int Quantity { get; set; }
+
+ [Range(1.0, double.MaxValue, ErrorMessage = "Price should be greater than zero.")]
+ public decimal Price { get; set; }
+ public DateTime CreatedAt { get; set; }
+
+ [Required(ErrorMessage = "User Id shouldn't be null")]
+ public Guid UserId { get; set; }
+ public User User { get; set; } = null!;
+
+ [Required(ErrorMessage = "Category Id shouldn't be null")]
+ public Guid CategoryId { get; set; }
+ public Category Category { get; set; } = null!;
+
+ // OrderDetails
+ public List? OrderDetails { get; set; }
+ }
+}
diff --git a/src/Entities/Booking.cs b/src/Entities/Booking.cs
new file mode 100644
index 0000000000000000000000000000000000000000..563b0faf87e9288aa78cbd8492392f0b3f0f1ef8
--- /dev/null
+++ b/src/Entities/Booking.cs
@@ -0,0 +1,32 @@
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
+
+namespace Backend_Teamwork.src.Entities
+{
+ public class Booking
+ {
+ public Guid Id { get; set; }
+
+ [Required(ErrorMessage = "Status shouldn't be null")]
+ public Status Status { get; set; }
+ public DateTime CreatedAt { get; set; }
+
+ [Required(ErrorMessage = "Workshop Id shouldn't be null")]
+ public Guid WorkshopId { get; set; }
+ public Workshop Workshop { get; set; } = null!;
+
+ [Required(ErrorMessage = "User Id shouldn't be null")]
+ public Guid UserId { get; set; }
+ public User User { get; set; } = null!;
+ public Payment? Payment { get; set; }
+ }
+
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public enum Status
+ {
+ Pending,
+ Confirmed,
+ Canceled,
+ Rejected,
+ }
+}
diff --git a/src/Entities/Category.cs b/src/Entities/Category.cs
new file mode 100644
index 0000000000000000000000000000000000000000..757b6fea2bbc8f1ee3e422f53eb511865d23ee0c
--- /dev/null
+++ b/src/Entities/Category.cs
@@ -0,0 +1,16 @@
+using System.ComponentModel.DataAnnotations;
+
+
+namespace Backend_Teamwork.src.Entities
+{
+ public class Category
+ {
+ public Guid Id { set; get; }
+ [
+ Required(ErrorMessage = "Name shouldn't be null"),
+ MinLength(2, ErrorMessage = "Name should be at at least 2 characters"),
+ MaxLength(10, ErrorMessage = "Name shouldn't be more than 10 characters")
+ ]
+ public string Name { set; get; }
+ }
+}
diff --git a/src/Entities/Order.cs b/src/Entities/Order.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c13657d35d44658f42f07274d7b6d0eae89cdbb4
--- /dev/null
+++ b/src/Entities/Order.cs
@@ -0,0 +1,28 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Backend_Teamwork.src.Entities
+{
+ public class Order
+ {
+ public Guid Id { get; set; }
+
+ [Range(1.0, double.MaxValue, ErrorMessage = "Total amount should be greater than zero.")]
+ public decimal TotalAmount { get; set; }
+
+ [
+ Required(ErrorMessage = "Address shouldn't be null"),
+ MinLength(10, ErrorMessage = "Address should be at at least 10 characters"),
+ MaxLength(30, ErrorMessage = "Address shouldn't be more than 30 characters")
+ ]
+ public string ShippingAddress { get; set; }
+ public DateTime CreatedAt { get; set; }
+
+ [Required(ErrorMessage = "User Id shouldn't be null")]
+ public Guid UserId { get; set; }
+ public User User { get; set; } = null!;
+
+ [Required(ErrorMessage = "Order details shouldn't be null")]
+ public List OrderDetails { get; set; }
+ public Payment? Payment { get; set; }
+ }
+}
diff --git a/src/Entities/OrderDetails.cs b/src/Entities/OrderDetails.cs
new file mode 100644
index 0000000000000000000000000000000000000000..44de2800756aff9d30a828812cdb2752d7b3f090
--- /dev/null
+++ b/src/Entities/OrderDetails.cs
@@ -0,0 +1,20 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Backend_Teamwork.src.Entities
+{
+ public class OrderDetails
+ {
+ public Guid Id { get; set; }
+ public Artwork Artwork { get; set; } = null!;
+
+ [Required(ErrorMessage = "Artwork Id shouldn't be null")]
+ public Guid ArtworkId { get; set; }
+ public Order Order { get; set; } = null!;
+
+ [Required(ErrorMessage = "Order Id shouldn't be null")]
+ public Guid OrderId { get; set; }
+
+ [Range(1, int.MaxValue, ErrorMessage = "Quantity should be greater than zero.")]
+ public int Quantity { get; set; }
+ }
+}
diff --git a/src/Entities/Payment.cs b/src/Entities/Payment.cs
new file mode 100644
index 0000000000000000000000000000000000000000..41469280b55621eb88c01ed049596215f0a142b5
--- /dev/null
+++ b/src/Entities/Payment.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Backend_Teamwork.src.Entities
+{
+ public class Payment
+ {
+ public Guid Id { get; set; }
+
+ [
+ Required(ErrorMessage = "Payment method shouldn't be null"),
+ MinLength(10, ErrorMessage = "Payment method should be at at least 10 characters"),
+ MaxLength(30, ErrorMessage = "Payment method shouldn't be more than 30 characters")
+ ]
+ public string PaymentMethod { get; set; }
+
+ [Range(1.0, double.MaxValue, ErrorMessage = "Amount should be greater than zero.")]
+ public decimal Amount { get; set; }
+ public DateTime CreatedAt { get; set; }
+ public Guid? OrderId { get; set; }
+ public Order? Order { get; set; }
+ public Guid? BookingId { get; set; }
+ public Booking? Booking { get; set; }
+ }
+}
diff --git a/src/Entities/User.cs b/src/Entities/User.cs
new file mode 100644
index 0000000000000000000000000000000000000000..551e309f1257fe0373337344b8a2641f1470bac9
--- /dev/null
+++ b/src/Entities/User.cs
@@ -0,0 +1,54 @@
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
+
+
+namespace Backend_Teamwork.src.Entities
+{
+ public class User
+ {
+ public Guid Id { get; set; }
+
+ [
+ Required(ErrorMessage = "Name shouldn't be null"),
+ MinLength(2, ErrorMessage = "Name should be at at least 2 characters"),
+ MaxLength(10, ErrorMessage = "Name shouldn't be more than 10 characters")
+ ]
+ public string Name { get; set; }
+
+ [
+ Required(ErrorMessage = "Phone number shouldn't be null"),
+ RegularExpression(
+ @"^\+966[5][0-9]{8}$",
+ ErrorMessage = "Phone number should be a valid Saudi phone number"
+ )
+ ]
+ public string PhoneNumber { get; set; }
+
+ [
+ Required(ErrorMessage = "Email shouldn't be null"),
+ EmailAddress(ErrorMessage = "Email should be with right format: @gmail.com")
+ ]
+ public string Email { get; set; }
+
+ [
+ Required(ErrorMessage = "Password shouldn't be null."),
+ MinLength(8, ErrorMessage = "Password should be at at least 8 characters")
+ ]
+ public string Password { get; set; }
+ public string? Description { set; get; }
+
+ [Required(ErrorMessage = "Salt shouldn't be null")]
+ public byte[]? Salt { get; set; }
+
+ [Required(ErrorMessage = "Role shouldn't be null")]
+ public UserRole Role { get; set; } = UserRole.Customer;
+
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public enum UserRole
+ {
+ Admin,
+ Customer,
+ Artist,
+ }
+ }
+}
diff --git a/src/Entities/Workshop.cs b/src/Entities/Workshop.cs
new file mode 100644
index 0000000000000000000000000000000000000000..86ff50a8227d3b27f62d17ac8f7fd27c083a81aa
--- /dev/null
+++ b/src/Entities/Workshop.cs
@@ -0,0 +1,50 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Backend_Teamwork.src.Entities
+{
+ public class Workshop
+ {
+ public Guid Id { get; set; }
+
+ [
+ Required(ErrorMessage = "Name shouldn't be null"),
+ MinLength(10, ErrorMessage = "Name should be at at least 10 characters"),
+ MaxLength(30, ErrorMessage = "Name shouldn't be more than 30 characters")
+ ]
+ public string Name { get; set; }
+
+ [
+ Required(ErrorMessage = "Location shouldn't be null"),
+ MinLength(10, ErrorMessage = "Location should be at at least 10 characters"),
+ MaxLength(30, ErrorMessage = "Location shouldn't be more than 30 characters")
+ ]
+ public string Location { get; set; }
+
+ [
+ Required(ErrorMessage = "Description shouldn't be null"),
+ MinLength(30, ErrorMessage = "Description should be at at least 30 characters"),
+ MaxLength(200, ErrorMessage = "Description shouldn't be more than 200 characters")
+ ]
+ public string Description { get; set; }
+
+ [Required(ErrorMessage = "StartTime shouldn't be null")]
+ public DateTime StartTime { get; set; }
+
+ [Required(ErrorMessage = "EndTime shouldn't be null")]
+ public DateTime EndTime { get; set; }
+
+ [Range(1.0, double.MaxValue, ErrorMessage = "Price should be greater than zero.")]
+ public decimal Price { get; set; }
+
+ [Range(1, int.MaxValue, ErrorMessage = "Capacity should be greater than zero.")]
+ public int Capacity { get; set; }
+
+ [Required(ErrorMessage = "Availability shouldn't be null")]
+ public bool Availability { get; set; }
+ public DateTime? CreatedAt { get; set; } = DateTime.Now;
+
+ [Required(ErrorMessage = "User Id shouldn't be null")]
+ public Guid UserId { get; set; }
+ public User User { get; set; } = null!;
+ }
+}
diff --git a/src/Middleware/ErrorHandlerMiddleware.cs b/src/Middleware/ErrorHandlerMiddleware.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1970e2abdb04df7b73ef66110b697d73e7e4abc1
--- /dev/null
+++ b/src/Middleware/ErrorHandlerMiddleware.cs
@@ -0,0 +1,21 @@
+using Backend_Teamwork.src.Utils;
+
+namespace Backend_Teamwork.src.Middleware
+{
+ public class ErrorHandlerMiddleware
+ {
+ private readonly RequestDelegate _next;
+ public ErrorHandlerMiddleware(RequestDelegate next){
+ _next = next;
+ }
+ public async Task InvokeAsync(HttpContext context){
+ try{
+ await _next(context);
+ }catch(CustomException ex){
+ context.Response.StatusCode = ex.StatusCode;
+ context.Response.ContentType = "application/json";
+ await context.Response.WriteAsync(ex.Message);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Program.cs b/src/Program.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5566461f52465aee159c284736ec620773e1ac8d
--- /dev/null
+++ b/src/Program.cs
@@ -0,0 +1,152 @@
+using System.Text;
+using System.Text.Json.Serialization;
+using Backend_Teamwork.src.Database;
+using Backend_Teamwork.src.Entities;
+using Backend_Teamwork.src.Middleware;
+using Backend_Teamwork.src.Repository;
+using Backend_Teamwork.src.Services.artwork;
+using Backend_Teamwork.src.Services.booking;
+using Backend_Teamwork.src.Services.category;
+using Backend_Teamwork.src.Services.order;
+using Backend_Teamwork.src.Services.user;
+using Backend_Teamwork.src.Services.workshop;
+using Backend_Teamwork.src.Utils;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.IdentityModel.Tokens;
+using Npgsql;
+using static Backend_Teamwork.src.Entities.User;
+
+var builder = WebApplication.CreateBuilder(args);
+
+//connect to database
+var dataSourceBuilder = new NpgsqlDataSourceBuilder(
+ builder.Configuration.GetConnectionString("Local")
+);
+dataSourceBuilder.MapEnum();
+dataSourceBuilder.MapEnum();
+
+//add database connection
+builder.Services.AddDbContext(options =>
+{
+ options.UseNpgsql(dataSourceBuilder.Build());
+});
+
+//add auto-mapper
+builder.Services.AddAutoMapper(typeof(MapperProfile).Assembly);
+
+//add DI services
+builder.Services.AddScoped().AddScoped();
+builder.Services.AddScoped().AddScoped();
+builder.Services.AddScoped().AddScoped();
+builder.Services.AddScoped().AddScoped();
+builder.Services.AddScoped().AddScoped();
+builder.Services.AddScoped().AddScoped();
+
+//builder.Services.AddScoped().AddScoped();
+
+
+//add logic for authentication
+builder
+ .Services.AddAuthentication(options =>
+ {
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+ options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
+ })
+ .AddJwtBearer(Options =>
+ {
+ Options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidateIssuer = true,
+ ValidateAudience = true,
+ ValidateLifetime = true,
+ ValidateIssuerSigningKey = true,
+ ValidIssuer = builder.Configuration["Jwt:Issuer"],
+ ValidAudience = builder.Configuration["Jwt:Audience"],
+ IssuerSigningKey = new SymmetricSecurityKey(
+ Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])
+ ),
+ };
+ });
+
+//add logic for athorization
+builder.Services.AddAuthorization(options =>
+{
+ options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
+ options.AddPolicy("CustomerOnly", policy => policy.RequireRole("Customer"));
+});
+
+// *** add CORS settings ***
+builder.Services.AddCors(options =>
+{
+ options.AddPolicy("AllowAll",
+ policyBuilder => policyBuilder.AllowAnyOrigin()
+ .AllowAnyHeader()
+ .AllowAnyMethod());
+});
+// builder.Services.AddCors(options =>
+// {
+// options.AddPolicy("AllowSpecificOrigin",
+// builder => builder.WithOrigins("http://localhost:5173")
+// .AllowAnyHeader()
+// .AllowAnyMethod());
+// });
+
+
+//add controllers
+builder.Services.AddControllers();
+builder.Services.AddEndpointsApiExplorer();
+builder
+ .Services.AddControllers()
+ .AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
+
+//add swagger
+builder.Services.AddSwaggerGen();
+
+var app = builder.Build();
+app.UseRouting();
+app.MapGet("/", () => "Server is running");
+
+//Convert to Timestamp format
+AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
+
+//
+
+//test database connection
+using (var scope = app.Services.CreateScope())
+{
+ var dbContext = scope.ServiceProvider.GetRequiredService();
+ try
+ {
+ if (dbContext.Database.CanConnect())
+ {
+ Console.WriteLine("Database is connected");
+ dbContext.Database.Migrate();
+ }
+ else
+ {
+ Console.WriteLine("Unable to connect to the database.");
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Database connection failed: {ex.Message}");
+ }
+}
+app.UseHttpsRedirection();
+
+//use middleware
+app.UseMiddleware();
+app.UseAuthentication();
+app.UseAuthorization();
+
+//use controllers
+app.MapControllers();
+
+//use swagger
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+app.Run();
diff --git a/src/Repository/ArtworkRepository.cs b/src/Repository/ArtworkRepository.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a28a0738227fe76cf84044134bcfcb6ccc6ce2dc
--- /dev/null
+++ b/src/Repository/ArtworkRepository.cs
@@ -0,0 +1,101 @@
+using Backend_Teamwork.src.Database;
+using Backend_Teamwork.src.Entities;
+using Backend_Teamwork.src.Utils;
+using Microsoft.EntityFrameworkCore;
+
+namespace Backend_Teamwork.src.Repository
+{
+ public class ArtworkRepository
+ {
+ private readonly DbSet _artwork;
+ private readonly DatabaseContext _databaseContext; // for dependency injection
+
+ // Dependency Injection
+ public ArtworkRepository(DatabaseContext databaseContext)
+ {
+ _databaseContext = databaseContext;
+ // initialize artwork table in the database
+ _artwork = databaseContext.Set();
+ }
+
+ // Methods
+ // create artwork
+ public async Task CreateOneAsync(Artwork newArtwork)
+ {
+ await _artwork.AddAsync(newArtwork);
+ await _databaseContext.SaveChangesAsync();
+ return await GetByIdAsync(newArtwork.Id);
+ }
+
+ // get all artworks
+ public async Task> GetAllAsync(PaginationOptions paginationOptions)
+ {
+ var artworkSearch = _artwork.Where(a =>
+ a.Title.ToLower().Contains(paginationOptions.Search.ToLower())
+ );
+
+ artworkSearch = artworkSearch.Where(a =>
+ a.Price >= paginationOptions.LowPrice && a.Price <= paginationOptions.HighPrice
+ );
+
+ artworkSearch = artworkSearch
+ .Skip((paginationOptions.PageNumber - 1) * paginationOptions.PageSize)
+ .Take(paginationOptions.PageSize);
+
+ artworkSearch = paginationOptions.SortOrder switch
+ {
+ "name_desc" => artworkSearch.OrderByDescending(a => a.Title),
+ "date" => artworkSearch.OrderBy(a => a.CreatedAt),
+ "date_desc" => artworkSearch.OrderByDescending(a => a.CreatedAt),
+ "price" => artworkSearch.OrderBy(a => a.Price),
+ "price_desc" => artworkSearch.OrderByDescending(a => a.Price),
+ _ => artworkSearch.OrderBy(a => a.Title),
+ };
+
+ return await artworkSearch.Include(o => o.Category).Include(o => o.User).ToListAsync();
+ }
+
+ // get artwork by id
+ public async Task GetByIdAsync(Guid id)
+ {
+ return await _artwork
+ .Include(a => a.Category)
+ .Include(a => a.User)
+ .FirstOrDefaultAsync(a => a.Id == id);
+ }
+
+ // get artworks by artist id
+ public async Task> GetByArtistIdAsync(Guid id)
+ {
+ return await _artwork.Include(a => a.Category).Where(a => a.UserId == id).ToListAsync();
+ }
+
+ // delete artwork
+ public async Task DeleteOneAsync(Artwork artwork)
+ {
+ _artwork.Remove(artwork);
+ await _databaseContext.SaveChangesAsync();
+ return true;
+ }
+
+ // update artwork
+ public async Task UpdateOneAsync(Artwork updateArtwork)
+ {
+ _artwork.Update(updateArtwork);
+ await _databaseContext.SaveChangesAsync();
+ return await GetByIdAsync(updateArtwork.Id);
+ }
+
+ // Count total artworks
+ public async Task CountAsync()
+ {
+ return await _artwork.CountAsync();
+ }
+
+ // Count artworks by artist
+ public async Task CountByArtistAsync(Guid artistId)
+ {
+ return await _artwork.CountAsync(a => a.UserId == artistId);
+ }
+ }
+}
diff --git a/src/Repository/BookingRepository.cs b/src/Repository/BookingRepository.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a466423f53ab94acea68b2b1e1888ae3d724c9c2
--- /dev/null
+++ b/src/Repository/BookingRepository.cs
@@ -0,0 +1,121 @@
+using Backend_Teamwork.src.Database;
+using Backend_Teamwork.src.Entities;
+using Backend_Teamwork.src.Utils;
+using Microsoft.EntityFrameworkCore;
+
+namespace Backend_Teamwork.src.Repository
+{
+ public class BookingRepository
+ {
+ private readonly DbSet _booking;
+ private readonly DatabaseContext _databaseContext;
+
+ public BookingRepository(DatabaseContext databaseContext)
+ {
+ _databaseContext = databaseContext;
+ _booking = databaseContext.Set();
+ }
+
+ public async Task> GetAllAsync()
+ {
+ return await _booking
+ .Include(b => b.User)
+ .Include(b => b.Workshop)
+ .ThenInclude(w => w.User)
+ .ToListAsync();
+ }
+
+ public async Task GetByIdAsync(Guid id)
+ {
+ return await _booking
+ .Include(b => b.User)
+ .Include(b => b.Workshop)
+ .ThenInclude(w => w.User)
+ .FirstOrDefaultAsync(b => b.Id == id);
+ }
+
+ public async Task> GetByUserIdAsync(Guid userId)
+ {
+ return await _booking
+ .Include(b => b.User)
+ .Include(b => b.Workshop)
+ .ThenInclude(w => w.User)
+ .Where(b => b.UserId == userId)
+ .ToListAsync();
+ }
+
+ public async Task> GetByStatusAsync(string status)
+ {
+ return await _booking
+ .Include(b => b.User)
+ .Include(b => b.Workshop)
+ .ThenInclude(w => w.User)
+ .Where(b => b.Status.ToString().ToLower() == status.ToLower())
+ .ToListAsync();
+ }
+
+ public async Task> GetByUserIdAndStatusAsync(Guid userId, string status)
+ {
+ return await _booking
+ .Include(b => b.User)
+ .Include(b => b.Workshop)
+ .ThenInclude(w => w.User)
+ .Where(b => b.Status.ToString() == status.ToString() && b.UserId == userId)
+ .ToListAsync();
+ }
+
+ public async Task> GetByWorkshopIdAndStatusAsync(
+ Guid workshopId,
+ Status status
+ )
+ {
+ return await _booking
+ .Where(b => b.WorkshopId == workshopId && b.Status == status)
+ .ToListAsync();
+ }
+
+ public async Task GetByUserIdAndWorkshopIdAsync(Guid userId, Guid workshopId)
+ {
+ return await _booking.AnyAsync(b => b.UserId == userId && b.WorkshopId == workshopId);
+ }
+
+ public async Task> GetWithPaginationAsync(PaginationOptions paginationOptions)
+ {
+ return await _booking
+ .Include(b => b.User)
+ .Include(b => b.Workshop)
+ .ThenInclude(w => w.User)
+ .Skip((paginationOptions.PageNumber - 1) * paginationOptions.PageSize)
+ .Take(paginationOptions.PageSize)
+ .ToListAsync();
+ }
+
+ public async Task> GetByUserIdWithPaginationAsync(
+ PaginationOptions paginationOptions
+ )
+ {
+ var bookings = _booking.Where(b => b.UserId.ToString() == paginationOptions.Search);
+ return await bookings
+ .Include(b => b.User)
+ .Include(b => b.Workshop)
+ .ThenInclude(w => w.User)
+ .Skip((paginationOptions.PageNumber - 1) * paginationOptions.PageSize)
+ .Take(paginationOptions.PageSize)
+ .ToListAsync();
+ }
+
+ public async Task CreateAsync(Booking booking)
+ {
+ await _booking.AddAsync(booking);
+ await _databaseContext.SaveChangesAsync();
+ return await GetByIdAsync(booking.Id);
+ }
+
+ public async Task UpdateAsync(Booking booking)
+ {
+ _booking.Update(booking);
+ await _databaseContext.SaveChangesAsync();
+ return await GetByIdAsync(booking.Id);
+ }
+ }
+}
diff --git a/src/Repository/CategoryRepository.cs b/src/Repository/CategoryRepository.cs
new file mode 100644
index 0000000000000000000000000000000000000000..320ca16359c6a5ee2fcf000586601913afe8d0f1
--- /dev/null
+++ b/src/Repository/CategoryRepository.cs
@@ -0,0 +1,68 @@
+using Backend_Teamwork.src.Database;
+using Backend_Teamwork.src.Entities;
+using Backend_Teamwork.src.Utils;
+using Microsoft.EntityFrameworkCore;
+
+namespace Backend_Teamwork.src.Repository
+{
+ public class CategoryRepository
+ {
+ private readonly DbSet _category;
+ private readonly DatabaseContext _databaseContext;
+
+ public CategoryRepository(DatabaseContext databaseContext)
+ {
+ _databaseContext = databaseContext;
+ _category = databaseContext.Set();
+ }
+
+ public async Task> GetAllAsync()
+ {
+ return await _category.ToListAsync();
+ }
+
+ public async Task GetByIdAsync(Guid id)
+ {
+ return await _category.FirstOrDefaultAsync(c => c.Id == id);
+ }
+
+ public async Task GetByNameAsync(string name)
+ {
+ return await _category.FirstOrDefaultAsync(c => c.Name.ToLower() == name.ToLower());
+ }
+
+ public async Task> GetWithPaginationAsync(PaginationOptions paginationOptions)
+ {
+ return await _category
+ .Skip((paginationOptions.PageNumber - 1) * paginationOptions.PageSize)
+ .Take(paginationOptions.PageSize)
+ .OrderBy(c => c.Name)
+ .ToListAsync();
+ }
+
+ public async Task> SortByNameAsync()
+ {
+ return await _category.OrderBy(c => c.Name).ToListAsync();
+ }
+
+ public async Task CreateAsync(Category category)
+ {
+ await _category.AddAsync(category);
+ await _databaseContext.SaveChangesAsync();
+ return category;
+ }
+
+ public async Task UpdateAsync(Category category)
+ {
+ _category.Update(category);
+ await _databaseContext.SaveChangesAsync();
+ return category;
+ }
+
+ public async Task DeleteAsync(Category category)
+ {
+ _category.Remove(category);
+ await _databaseContext.SaveChangesAsync();
+ }
+ }
+}
diff --git a/src/Repository/OrderRepository.cs b/src/Repository/OrderRepository.cs
new file mode 100644
index 0000000000000000000000000000000000000000..77cb0796bce6ccc9b17b4d231de1bae82b62d59c
--- /dev/null
+++ b/src/Repository/OrderRepository.cs
@@ -0,0 +1,105 @@
+using Backend_Teamwork.src.Database;
+using Backend_Teamwork.src.Entities;
+using Backend_Teamwork.src.Utils;
+using Microsoft.EntityFrameworkCore;
+
+namespace Backend_Teamwork.src.Repository
+{
+ public class OrderRepository
+ {
+ private readonly DbSet _order;
+ private readonly DatabaseContext _databaseContext;
+
+ public OrderRepository(DatabaseContext databaseContext)
+ {
+ _databaseContext = databaseContext;
+ _order = databaseContext.Set();
+ }
+
+ public async Task> GetAllAsync()
+ {
+ // Include User, OrderDetails, Artwork, and Category
+ return await _order
+ .Include(o => o.User) // Include User details
+ .Include(o => o.OrderDetails)
+ .ThenInclude(od => od.Artwork) // Include Artwork
+ .ThenInclude(a => a.Category) // Include Category if it's a related entity
+ .ToListAsync();
+ }
+
+ public async Task> GetOrdersByUserIdAsync(Guid userId)
+ {
+ return await _databaseContext
+ .Order.Include(o => o.User) // Include User
+ .Include(o => o.OrderDetails)
+ .ThenInclude(od => od.Artwork) // Include Artwork
+ .ThenInclude(a => a.Category) // Include Category in Artwork
+ .Where(order => order.UserId == userId)
+ .ToListAsync();
+ }
+
+ public async Task CreateOneAsync(Order newOrder)
+ {
+ await _order.AddAsync(newOrder);
+ await _databaseContext.SaveChangesAsync();
+ return await GetByIdAsync(newOrder.Id);
+ }
+
+ public async Task GetByIdAsync(Guid id)
+ {
+ return await _order
+ .Include(o => o.User) // Include User
+ .Include(o => o.OrderDetails)
+ .ThenInclude(od => od.Artwork) // Include Artwork
+ .ThenInclude(a => a.Category) // Include Category in Artwork
+ .FirstOrDefaultAsync(o => o.Id == id);
+ }
+
+ public async Task DeleteOneAsync(Order Order)
+ {
+ if (Order == null)
+ return false;
+ _order.Remove(Order);
+ await _databaseContext.SaveChangesAsync();
+ return true;
+ }
+
+ public async Task UpdateOneAsync(Order updateOrder)
+ {
+ if (updateOrder == null)
+ return false;
+ _order.Update(updateOrder);
+ await _databaseContext.SaveChangesAsync();
+ return true;
+ }
+
+ public async Task> GetAllAsync(PaginationOptions paginationOptions)
+ {
+ // Query for orders with optional search
+ var orderQuery = _order
+ .Include(o => o.OrderDetails) // Include order details
+ .Include(o => o.User)
+ .Where(o =>
+ o.ShippingAddress.Contains(paginationOptions.Search)
+ || o.TotalAmount.ToString().Contains(paginationOptions.Search)
+ );
+
+ // Apply pagination
+ orderQuery = orderQuery
+ .Skip((paginationOptions.PageNumber - 1) * paginationOptions.PageSize)
+ .Take(paginationOptions.PageSize);
+
+ // Sorting logic
+ orderQuery = paginationOptions.SortOrder switch
+ {
+ "amount_desc" => orderQuery.OrderByDescending(o => o.TotalAmount),
+ "amount_asc" => orderQuery.OrderBy(o => o.TotalAmount),
+ "date_desc" => orderQuery.OrderByDescending(o => o.CreatedAt),
+ "date_asc" => orderQuery.OrderBy(o => o.CreatedAt),
+ _ => orderQuery.OrderBy(o => o.ShippingAddress), // Default sorting by ShippingAddress
+ };
+
+ return await orderQuery.ToListAsync();
+ }
+ }
+}
diff --git a/src/Repository/PaymentRepository.cs b/src/Repository/PaymentRepository.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3ac3d834fed60a610f245b36281f8474e3ff7074
--- /dev/null
+++ b/src/Repository/PaymentRepository.cs
@@ -0,0 +1,59 @@
+using Backend_Teamwork.src.Database;
+using Backend_Teamwork.src.Entities;
+using Microsoft.EntityFrameworkCore;
+
+namespace Backend_Teamwork.src.Repository
+{
+ public class PaymentRepository
+ {
+ private readonly DbSet _payment;
+ private readonly DatabaseContext _databaseContext;
+
+ public PaymentRepository(DatabaseContext databaseContext)
+ {
+ _databaseContext = databaseContext;
+ _payment = databaseContext.Set();
+ }
+
+ // create in database
+ public async Task CreateOneAsync(Payment newPayment)
+ {
+ await _payment.AddAsync(newPayment);
+ await _databaseContext.SaveChangesAsync();
+ return await GetByIdAsync(newPayment.Id);
+ }
+
+ // get by id
+ public async Task GetByIdAsync(Guid id)
+ {
+ return await _payment
+ .Include(p => p.Order)
+ .Include(p => p.Booking)
+ .FirstOrDefaultAsync(p => p.Id == id);
+ }
+
+ // delete
+ public async Task DeleteOneAsync(Payment deletePayment)
+ {
+ _payment.Remove(deletePayment);
+ await _databaseContext.SaveChangesAsync();
+ return true;
+ }
+
+ // update
+ public async Task UpdateOneAsync(Payment updatePayment)
+ {
+ if (updatePayment == null)
+ return false;
+ _payment.Update(updatePayment);
+ await _databaseContext.SaveChangesAsync();
+ return true;
+ }
+
+ // get all
+ public async Task> GetAllAsync()
+ {
+ return await _payment.Include(p => p.Order).Include(p => p.Booking).ToListAsync();
+ }
+ }
+}
diff --git a/src/Repository/UserRepository.cs b/src/Repository/UserRepository.cs
new file mode 100644
index 0000000000000000000000000000000000000000..836277c0c992f05167d2b73e16ed0755d64e5c82
--- /dev/null
+++ b/src/Repository/UserRepository.cs
@@ -0,0 +1,96 @@
+using Backend_Teamwork.src.Database;
+using Backend_Teamwork.src.Entities;
+using Backend_Teamwork.src.Utils;
+using Microsoft.EntityFrameworkCore;
+
+namespace Backend_Teamwork.src.Repository
+{
+ public class UserRepository
+ {
+ private readonly DbSet _user;
+ private readonly DatabaseContext _databaseContext;
+
+ public UserRepository(DatabaseContext databaseContext)
+ {
+ _databaseContext = databaseContext;
+ _user = databaseContext.Set();
+ }
+
+ public async Task> GetAllAsync(PaginationOptions paginationOptions)
+ {
+ // Combined search logic with OR for name, email, or phone number
+ var userQuery = _user.Where(a =>
+ a.Name.ToLower().Contains(paginationOptions.Search.ToLower())
+ || a.Email.ToLower().Contains(paginationOptions.Search.ToLower())
+ || a.PhoneNumber.Contains(paginationOptions.Search) // if u try this the dont put the number with +, it does not work
+ // I think I need to add the country code in the front-end part for the phone number search.
+ );
+
+ // Apply pagination
+ userQuery = userQuery
+ .Skip((paginationOptions.PageNumber - 1) * paginationOptions.PageSize)
+ .Take(paginationOptions.PageSize);
+
+ // Sorting logic
+ userQuery = paginationOptions.SortOrder switch
+ {
+ "name_desc" => userQuery.OrderByDescending(a => a.Name),
+ "email_desc" => userQuery.OrderByDescending(a => a.Email),
+ "email_asc" => userQuery.OrderBy(a => a.Email),
+ _ => userQuery.OrderBy(a => a.Name), // Default to ascending by name
+ };
+
+ return await userQuery.ToListAsync();
+ }
+
+ public async Task GetCountAsync()
+ {
+ return await _user.CountAsync();
+ }
+
+ public async Task CreateOneAsync(User newUser)
+ {
+ await _user.AddAsync(newUser);
+ await _databaseContext.SaveChangesAsync();
+ return newUser;
+ }
+
+ public async Task GetByIdAsync(Guid id)
+ {
+ return await _user.FindAsync(id);
+ }
+
+ public async Task DeleteOneAsync(User User)
+ {
+ if (User == null)
+ return false;
+ _user.Remove(User);
+ await _databaseContext.SaveChangesAsync();
+ return true;
+ }
+
+ public async Task UpdateOneAsync(User updateUser)
+ {
+ if (updateUser == null)
+ return false;
+ _user.Update(updateUser);
+ await _databaseContext.SaveChangesAsync();
+ return true;
+ }
+
+ public async Task GetByEmailAsync(string email)
+ {
+ return await _user.FirstOrDefaultAsync(c => c.Email == email);
+ }
+
+ public async Task GetByPhoneNumberAsync(string phoneNumber)
+ {
+ return await _user.FirstOrDefaultAsync(c => c.PhoneNumber == phoneNumber);
+ }
+
+ public async Task GetByNameAsync(string name)
+ {
+ return await _user.FirstOrDefaultAsync(c => c.Name == name);
+ }
+ }
+}
diff --git a/src/Repository/WorkshopRepository.cs b/src/Repository/WorkshopRepository.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7036333a1eca6ecbbea67c5f5c916251c0ce21ff
--- /dev/null
+++ b/src/Repository/WorkshopRepository.cs
@@ -0,0 +1,87 @@
+using Backend_Teamwork.src.Database;
+using Backend_Teamwork.src.Entities;
+using Backend_Teamwork.src.Utils;
+using Microsoft.EntityFrameworkCore;
+
+namespace Backend_Teamwork.src.Repository
+{
+ public class WorkshopRepository
+ {
+ private readonly DbSet _workshops;
+ private readonly DatabaseContext _databaseContext;
+
+ public WorkshopRepository(DatabaseContext databaseContext)
+ {
+ _databaseContext = databaseContext;
+ _workshops = databaseContext.Set();
+ }
+
+ // create in database
+ public async Task CreateOneAsync(Workshop newWorkshop)
+ {
+ await _workshops.AddAsync(newWorkshop);
+ await _databaseContext.SaveChangesAsync();
+ return await GetByIdAsync(newWorkshop.Id);
+ }
+
+ // get by id
+ public async Task GetByIdAsync(Guid id)
+ {
+ return await _workshops.Include(o => o.User).FirstOrDefaultAsync(o => o.Id == id);
+ }
+
+ // delete
+ public async Task DeleteOneAsync(Workshop deleteWorkshop)
+ {
+ _workshops.Remove(deleteWorkshop);
+ await _databaseContext.SaveChangesAsync();
+ return true;
+ }
+
+ // update
+ public async Task UpdateOneAsync(Workshop updateWorkshop)
+ {
+ if (updateWorkshop == null)
+ return false;
+ _workshops.Update(updateWorkshop);
+ await _databaseContext.SaveChangesAsync();
+ return true;
+ }
+
+ // get all
+ public async Task> GetAllAsync()
+ {
+ return await _workshops.Include(o => o.User).ToListAsync();
+ }
+
+ public async Task> GetAllAsync(PaginationOptions paginationOptions)
+ {
+ // Combined search logic with OR for name, email, or phone number
+ var userQuery = _workshops.Where(a =>
+ a.Name.ToLower().Contains(paginationOptions.Search.ToLower())
+ || a.Location.ToLower().Contains(paginationOptions.Search.ToLower())
+ );
+
+ // Apply pagination
+ userQuery = userQuery
+ .Skip((paginationOptions.PageNumber - 1) * paginationOptions.PageSize)
+ .Take(paginationOptions.PageSize);
+
+ // Apply sorting logic
+ userQuery = paginationOptions.SortOrder switch
+ {
+ "name_desc" => userQuery.OrderByDescending(a => a.Name),
+ "location_asc" => userQuery.OrderBy(a => a.Location),
+ "location_desc" => userQuery.OrderByDescending(a => a.Location),
+ "price_desc" => userQuery.OrderByDescending(a => a.Price),
+ "price_asc" => userQuery.OrderBy(a => a.Price),
+ "date_desc" => userQuery.OrderByDescending(a => a.CreatedAt),
+ "date_asc" => userQuery.OrderBy(a => a.CreatedAt),
+ "capacity_desc" => userQuery.OrderByDescending(a => a.Capacity),
+ _ => userQuery.OrderBy(a => a.Name), // Default to ascending by name
+ };
+
+ return await userQuery.ToListAsync();
+ }
+ }
+}
diff --git a/src/Services/artwork/ArtworkService.cs b/src/Services/artwork/ArtworkService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1f83276e481cac6b04025c940f3a00d40c9325fe
--- /dev/null
+++ b/src/Services/artwork/ArtworkService.cs
@@ -0,0 +1,122 @@
+using AutoMapper;
+using Backend_Teamwork.src.Entities;
+using Backend_Teamwork.src.Repository;
+using Backend_Teamwork.src.Utils;
+using static Backend_Teamwork.src.DTO.ArtworkDTO;
+using static Backend_Teamwork.src.Entities.User;
+
+namespace Backend_Teamwork.src.Services.artwork
+{
+ public class ArtworkService : IArtworkService
+ {
+ private readonly ArtworkRepository _artworkRepo;
+ private readonly UserRepository _userRepo;
+ private readonly CategoryRepository _categoryRepo;
+ private readonly IMapper _mapper;
+
+ public ArtworkService(
+ ArtworkRepository artworkRepo,
+ UserRepository userRepo,
+ CategoryRepository categoryRepo,
+ IMapper mapper
+ )
+ {
+ _artworkRepo = artworkRepo;
+ _userRepo = userRepo;
+ _categoryRepo = categoryRepo;
+ _mapper = mapper;
+ }
+
+ public async Task CreateOneAsync(Guid artistId, ArtworkCreateDto createDto)
+ {
+ var foundCategory = await _categoryRepo.GetByIdAsync(createDto.CategoryId);
+ if (foundCategory == null)
+ {
+ throw CustomException.NotFound($"Category with id: {createDto.CategoryId} not found");
+ }
+ var artwork = _mapper.Map(createDto);
+ artwork.UserId = artistId;
+ var createdArtwork = await _artworkRepo.CreateOneAsync(artwork);
+ return _mapper.Map(createdArtwork);
+ }
+
+ public async Task> GetAllAsync(PaginationOptions paginationOptions)
+ {
+ if (paginationOptions.PageSize <= 0)
+ {
+ throw CustomException.BadRequest("PageSize should be greater than 0.");
+ }
+
+ if (paginationOptions.PageNumber <= 0)
+ {
+ throw CustomException.BadRequest("PageNumber should be greater than 0.");
+ }
+
+ var artworkList = await _artworkRepo.GetAllAsync(paginationOptions);
+ if (artworkList == null || !artworkList.Any())
+ {
+ throw CustomException.NotFound("No artworks found.");
+ }
+ return _mapper.Map, List>(artworkList);
+ }
+
+ public async Task GetByIdAsync(Guid id)
+ {
+ var artwork = await _artworkRepo.GetByIdAsync(id);
+ if (artwork == null)
+ {
+ throw CustomException.NotFound($"Artwork with id: {id} not found");
+ }
+ return _mapper.Map(artwork);
+ }
+
+ public async Task> GetByArtistIdAsync(Guid id)
+ {
+ var user = await _userRepo.GetByIdAsync(id)
+ ?? throw CustomException.NotFound($"User with id: {id} not found");
+ if (user.Role.ToString() != UserRole.Artist.ToString())
+ {
+ throw CustomException.BadRequest($"User with id: {id} is not an Artist");
+ }
+
+ var artworks = await _artworkRepo.GetByArtistIdAsync(id)
+ ?? throw CustomException.NotFound($"Artist with id: {id} has no artworks");
+
+ return _mapper.Map, List>(artworks);
+ }
+
+ public async Task DeleteOneAsync(Guid id)
+ {
+ var foundArtwork = await _artworkRepo.GetByIdAsync(id);
+ if (foundArtwork == null)
+ {
+ throw CustomException.NotFound($"Artwork with id: {id} not found");
+ }
+ bool isDeleted = await _artworkRepo.DeleteOneAsync(foundArtwork);
+ return isDeleted;
+ }
+
+ public async Task UpdateOneAsync(Guid id, ArtworkUpdateDTO updateDto)
+ {
+ var foundArtwork = await _artworkRepo.GetByIdAsync(id);
+ if (foundArtwork == null)
+ {
+ throw CustomException.NotFound($"Artwork with id: {id} not found");
+ }
+
+ _mapper.Map(updateDto, foundArtwork);
+ var updatedArtwork = await _artworkRepo.UpdateOneAsync(foundArtwork);
+ return _mapper.Map(updatedArtwork);
+ }
+
+ public async Task GetArtworkCountAsync()
+ {
+ return await _artworkRepo.CountAsync();
+ }
+
+ public async Task GetArtworkCountByArtistAsync(Guid artistId)
+ {
+ return await _artworkRepo.CountByArtistAsync(artistId);
+ }
+ }
+}
diff --git a/src/Services/artwork/IArtworkService.cs b/src/Services/artwork/IArtworkService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..523f54d29d676194494092eb21f2d325293653e2
--- /dev/null
+++ b/src/Services/artwork/IArtworkService.cs
@@ -0,0 +1,19 @@
+using Backend_Teamwork.src.Utils;
+using static Backend_Teamwork.src.DTO.ArtworkDTO;
+
+namespace Backend_Teamwork.src.Services.artwork
+{
+ public interface IArtworkService
+ {
+ Task CreateOneAsync(Guid userId, ArtworkCreateDto artwork);
+ Task> GetAllAsync(PaginationOptions paginationOptions);
+ Task GetByIdAsync(Guid id);
+ Task> GetByArtistIdAsync(Guid id);
+ Task DeleteOneAsync(Guid id);
+ Task UpdateOneAsync(Guid id, ArtworkUpdateDTO updateArtwork);
+
+ // New methods for counting
+ Task GetArtworkCountAsync(); // Get total count of artworks
+ Task GetArtworkCountByArtistAsync(Guid artistId); // Get count of artworks by a specific artist
+ }
+}
diff --git a/src/Services/booking/BookingService.cs b/src/Services/booking/BookingService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8e61ce176701e0c65af4eec27358c3706674380e
--- /dev/null
+++ b/src/Services/booking/BookingService.cs
@@ -0,0 +1,253 @@
+using AutoMapper;
+using Backend_Teamwork.src.Entities;
+using Backend_Teamwork.src.Repository;
+using Backend_Teamwork.src.Utils;
+using static Backend_Teamwork.src.DTO.BookingDTO;
+using static Backend_Teamwork.src.Entities.User;
+
+namespace Backend_Teamwork.src.Services.booking
+{
+ public class BookingService : IBookingService
+ {
+ private readonly BookingRepository _bookingRepository;
+ private readonly WorkshopRepository _workshopRepository;
+ private readonly PaymentRepository _paymentRepository;
+
+ private readonly IMapper _mapper;
+
+ public BookingService(
+ BookingRepository bookingRepository,
+ WorkshopRepository workshopRepository,
+ IMapper mapper
+ )
+ {
+ _bookingRepository = bookingRepository;
+ _workshopRepository = workshopRepository;
+ _mapper = mapper;
+ }
+
+ public async Task> GetAllAsync()
+ {
+ var bookings = await _bookingRepository.GetAllAsync();
+ if (bookings.Count == 0)
+ {
+ throw CustomException.NotFound($"Bookings not found");
+ }
+ return _mapper.Map, List>(bookings);
+ }
+
+ public async Task GetByIdAsync(Guid id, Guid userId, string userRole)
+ {
+ var booking = await _bookingRepository.GetByIdAsync(id);
+ if (booking == null)
+ {
+ throw CustomException.NotFound($"Booking with id: {id} not found");
+ }
+ if (userRole != UserRole.Admin.ToString() && booking.UserId != userId)
+ {
+ throw CustomException.Forbidden($"Not allowed to access booking with id: {id}");
+ }
+ return _mapper.Map(booking);
+ }
+
+ public async Task> GetByUserIdAsync(Guid userId)
+ {
+ var bookings = await _bookingRepository.GetByUserIdAsync(userId);
+ if (bookings.Count == 0)
+ {
+ throw CustomException.NotFound(
+ $"Bookings associated with userId: {userId} not found"
+ );
+ }
+ return _mapper.Map, List>(bookings);
+ }
+
+ public async Task> GetByStatusAsync(string status)
+ {
+ var bookings = await _bookingRepository.GetByStatusAsync(status);
+ if (bookings.Count == 0)
+ {
+ throw CustomException.NotFound($"Bookings with status: {status} not found");
+ }
+ return _mapper.Map, List>(bookings);
+ }
+
+ public async Task> GetByUserIdAndStatusAsync(
+ Guid userId,
+ string status
+ )
+ {
+ var bookings = await _bookingRepository.GetByUserIdAndStatusAsync(userId, status);
+ if (bookings.Count == 0)
+ {
+ throw CustomException.NotFound($"Bookings with status: {status} not found");
+ }
+ return _mapper.Map, List>(bookings);
+ }
+
+ public async Task> GetWithPaginationAsync(
+ PaginationOptions paginationOptions
+ )
+ {
+ var bookings = await _bookingRepository.GetWithPaginationAsync(paginationOptions);
+ if (bookings.Count == 0)
+ {
+ throw CustomException.NotFound($"Bookings not found");
+ }
+ return _mapper.Map, List>(bookings);
+ }
+
+ public async Task> GetByUserIdWithPaginationAsync(
+ PaginationOptions paginationOptions
+ )
+ {
+ var bookings = await _bookingRepository.GetByUserIdWithPaginationAsync(
+ paginationOptions
+ );
+ if (bookings.Count == 0)
+ {
+ throw CustomException.NotFound($"Bookings not found");
+ }
+ return _mapper.Map, List>(bookings);
+ }
+
+ public async Task CreateAsync(BookingCreateDto booking, Guid userId)
+ {
+ //1. check if the workshop is found
+ var workshop = await _workshopRepository.GetByIdAsync(booking.WorkshopId);
+ if (workshop == null)
+ {
+ throw CustomException.NotFound($"Workshp with id: {booking.WorkshopId} not found");
+ }
+ //2. check if the workshop isn't available
+ if (!workshop.Availability)
+ {
+ throw CustomException.BadRequest($"Invalid booking");
+ }
+ //3. check if the user already enrolled in this workshop
+ bool isFound = await _bookingRepository.GetByUserIdAndWorkshopIdAsync(
+ userId,
+ booking.WorkshopId
+ );
+ if (isFound)
+ {
+ throw CustomException.BadRequest($"Invalid booking");
+ }
+ //4. check if the user enrolled in other workshop at the same time
+ var workshops = await _workshopRepository.GetAllAsync();
+ var foundWorkshop = workshops.FirstOrDefault(w =>
+ (w.StartTime == workshop.StartTime && w.EndTime == workshop.EndTime)
+ || (w.StartTime < workshop.StartTime && w.EndTime > workshop.StartTime)
+ || (w.StartTime < workshop.EndTime && w.EndTime > workshop.EndTime)
+ );
+ var isFound2 = false;
+ if (foundWorkshop != null)
+ {
+ isFound2 = await _bookingRepository.GetByUserIdAndWorkshopIdAsync(
+ userId,
+ foundWorkshop.Id
+ );
+ }
+ if (isFound2)
+ {
+ throw CustomException.BadRequest($"Invalid booking");
+ }
+ //create booking
+ var mappedBooking = _mapper.Map(booking);
+ mappedBooking.UserId = userId;
+ mappedBooking.Status = Status.Pending;
+ var createdBooking = await _bookingRepository.CreateAsync(mappedBooking);
+ return _mapper.Map(createdBooking);
+ }
+
+ //after payment
+ public async Task ConfirmAsync(Guid id)
+ {
+ var booking = await _bookingRepository.GetByIdAsync(id);
+ if (booking == null)
+ {
+ throw CustomException.NotFound($"Booking with id: {id} not found");
+ }
+ //1. check if the workshop isn't available
+ if (!booking.Workshop.Availability)
+ {
+ throw CustomException.BadRequest($"Invalid confirming");
+ }
+ //2. check if the booking status isn't pending
+ if (booking.Status.ToString() != Status.Pending.ToString())
+ {
+ throw CustomException.BadRequest($"Invalid confirming");
+ }
+ //3. check if the user doesn't pay
+ //var payment =
+
+ //confirm booking
+ booking.Status = Status.Confirmed;
+ var updatedBooking = await _bookingRepository.UpdateAsync(booking);
+ return _mapper.Map(updatedBooking);
+ }
+
+ //after workshop becomes unavailable
+ public async Task> RejectAsync(Guid workshopId)
+ {
+ var workshop = await _workshopRepository.GetByIdAsync(workshopId);
+ //1. check if the workshop is found
+ if (workshop == null)
+ {
+ throw CustomException.NotFound($"Workshp with id: {workshopId} not found");
+ }
+ //2. check if the workshop is available
+ if (workshop.Availability)
+ {
+ throw CustomException.BadRequest($"Invalid regecting");
+ }
+ var bookings = await _bookingRepository.GetByWorkshopIdAndStatusAsync(
+ workshopId,
+ Status.Pending
+ );
+ //3. check if there is booking with Pending Status
+ if (bookings == null)
+ {
+ throw CustomException.BadRequest($"Invalid regecting");
+ }
+ foreach (var booking in bookings)
+ {
+ //reject booking
+ booking.Status = Status.Rejected;
+ var updatedBooking = await _bookingRepository.UpdateAsync(booking);
+ }
+ return _mapper.Map, List>(bookings);
+ }
+
+ public async Task CancelAsync(Guid id, Guid userId)
+ {
+ var booking = await _bookingRepository.GetByIdAsync(id);
+ if (booking == null)
+ {
+ throw CustomException.NotFound($"Booking with id: {id} not found");
+ }
+ //1. check if the booking belongs to the user
+ if (booking.UserId != userId)
+ {
+ throw CustomException.BadRequest($"Invalid canceling");
+ }
+ //2. check if the workshop is available
+ if (!booking.Workshop.Availability)
+ {
+ throw CustomException.BadRequest($"Invalid canceling");
+ }
+ //3. check if the booking status isn't pending
+ if (booking.Status.ToString() != Status.Pending.ToString())
+ {
+ throw CustomException.BadRequest($"Invalid canceling");
+ }
+ //4. check if the user pay
+ //var payment =
+
+ //Cancel booking
+ booking.Status = Status.Canceled;
+ var updatedBooking = await _bookingRepository.UpdateAsync(booking);
+ return _mapper.Map(updatedBooking);
+ }
+ }
+}
diff --git a/src/Services/booking/IBookingService.cs b/src/Services/booking/IBookingService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c2025c1061f5dc4f91f63822bebf42bc52bd12ee
--- /dev/null
+++ b/src/Services/booking/IBookingService.cs
@@ -0,0 +1,22 @@
+using Backend_Teamwork.src.Utils;
+using static Backend_Teamwork.src.DTO.BookingDTO;
+
+namespace Backend_Teamwork.src.Services.booking
+{
+ public interface IBookingService
+ {
+ Task> GetAllAsync();
+ Task GetByIdAsync(Guid id, Guid userId, string userRole);
+ Task> GetByUserIdAsync(Guid userId);
+ Task