diff --git a/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada-async.docker.yml b/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada-async.docker.yml
index 2ec673547..f2c536b46 100644
--- a/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada-async.docker.yml
+++ b/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada-async.docker.yml
@@ -168,22 +168,46 @@ modules:
downstream: reports-service/reports
auth: true
- - upstream: /
- method: GET
+ - upstream: /{reportId}
+ method: DELETE
use: downstream
- downstream: reports-service/reports
+ downstream: reports-service/reports/{reportId}
auth: true
- - upstream: /
- method: PUT
+ - upstream: /search
+ method: POST
use: downstream
- downstream: reports-service/reports
+ downstream: reports-service/reports/search
auth: true
- - upstream: /{reportId}
+ - upstream: /{reportId}/cancel
+ method: POST
+ use: downstream
+ downstream: reports-service/reports/{reportId}/cancel
+ auth: true
+
+ - upstream: /{reportId}/start-review
+ method: POST
+ use: downstream
+ downstream: reports-service/reports/{reportId}/start-review
+ auth: true
+
+ - upstream: /{reportId}/resolve
+ method: POST
+ use: downstream
+ downstream: reports-service/reports/{reportId}/resolve
+ auth: true
+
+ - upstream: /{reportId}/reject
+ method: POST
+ use: downstream
+ downstream: reports-service/reports/{reportId}/reject
+ auth: true
+
+ - upstream: /students/{studentId}
method: GET
use: downstream
- downstream: reports-service/reports/{reportId}
+ downstream: reports-service/reports/students/{studentId}
auth: true
services:
diff --git a/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada-async.yml b/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada-async.yml
index 2323905eb..d84e63a1d 100644
--- a/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada-async.yml
+++ b/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada-async.yml
@@ -168,22 +168,46 @@ modules:
downstream: reports-service/reports
auth: true
- - upstream: /
- method: GET
+ - upstream: /{reportId}
+ method: DELETE
use: downstream
- downstream: reports-service/reports
+ downstream: reports-service/reports/{reportId}
auth: true
- - upstream: /
- method: PUT
+ - upstream: /search
+ method: POST
use: downstream
- downstream: reports-service/reports
+ downstream: reports-service/reports/search
auth: true
- - upstream: /{reportId}
+ - upstream: /{reportId}/cancel
+ method: POST
+ use: downstream
+ downstream: reports-service/reports/{reportId}/cancel
+ auth: true
+
+ - upstream: /{reportId}/start-review
+ method: POST
+ use: downstream
+ downstream: reports-service/reports/{reportId}/start-review
+ auth: true
+
+ - upstream: /{reportId}/resolve
+ method: POST
+ use: downstream
+ downstream: reports-service/reports/{reportId}/resolve
+ auth: true
+
+ - upstream: /{reportId}/reject
+ method: POST
+ use: downstream
+ downstream: reports-service/reports/{reportId}/reject
+ auth: true
+
+ - upstream: /students/{studentId}
method: GET
use: downstream
- downstream: reports-service/reports/{reportId}
+ downstream: reports-service/reports/students/{studentId}
auth: true
services:
diff --git a/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada.docker.yml b/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada.docker.yml
index 8dfc7384e..ab080fde2 100644
--- a/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada.docker.yml
+++ b/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada.docker.yml
@@ -145,22 +145,46 @@ modules:
downstream: reports-service/reports
auth: true
- - upstream: /
- method: GET
+ - upstream: /{reportId}
+ method: DELETE
use: downstream
- downstream: reports-service/reports
+ downstream: reports-service/reports/{reportId}
auth: true
- - upstream: /
- method: PUT
+ - upstream: /search
+ method: POST
use: downstream
- downstream: reports-service/reports
+ downstream: reports-service/reports/search
auth: true
- - upstream: /{reportId}
+ - upstream: /{reportId}/cancel
+ method: POST
+ use: downstream
+ downstream: reports-service/reports/{reportId}/cancel
+ auth: true
+
+ - upstream: /{reportId}/start-review
+ method: POST
+ use: downstream
+ downstream: reports-service/reports/{reportId}/start-review
+ auth: true
+
+ - upstream: /{reportId}/resolve
+ method: POST
+ use: downstream
+ downstream: reports-service/reports/{reportId}/resolve
+ auth: true
+
+ - upstream: /{reportId}/reject
+ method: POST
+ use: downstream
+ downstream: reports-service/reports/{reportId}/reject
+ auth: true
+
+ - upstream: /students/{studentId}
method: GET
use: downstream
- downstream: reports-service/reports/{reportId}
+ downstream: reports-service/reports/students/{studentId}
auth: true
services:
diff --git a/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada.yml b/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada.yml
index 0170714a5..a3ea70302 100644
--- a/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada.yml
+++ b/MiniSpace.APIGateway/src/MiniSpace.APIGateway/ntrada.yml
@@ -147,22 +147,46 @@ modules:
downstream: reports-service/reports
auth: true
- - upstream: /
- method: GET
+ - upstream: /{reportId}
+ method: DELETE
use: downstream
- downstream: reports-service/reports
+ downstream: reports-service/reports/{reportId}
auth: true
- - upstream: /
- method: PUT
+ - upstream: /search
+ method: POST
use: downstream
- downstream: reports-service/reports
+ downstream: reports-service/reports/search
auth: true
- - upstream: /{reportId}
+ - upstream: /{reportId}/cancel
+ method: POST
+ use: downstream
+ downstream: reports-service/reports/{reportId}/cancel
+ auth: true
+
+ - upstream: /{reportId}/start-review
+ method: POST
+ use: downstream
+ downstream: reports-service/reports/{reportId}/start-review
+ auth: true
+
+ - upstream: /{reportId}/resolve
+ method: POST
+ use: downstream
+ downstream: reports-service/reports/{reportId}/resolve
+ auth: true
+
+ - upstream: /{reportId}/reject
+ method: POST
+ use: downstream
+ downstream: reports-service/reports/{reportId}/reject
+ auth: true
+
+ - upstream: /students/{studentId}
method: GET
use: downstream
- downstream: reports-service/reports/{reportId}
+ downstream: reports-service/reports/students/{studentId}
auth: true
services:
diff --git a/MiniSpace.Services.Reports/MiniSpace.Services.Reports.sln b/MiniSpace.Services.Reports/MiniSpace.Services.Reports.sln
new file mode 100644
index 000000000..b6dc505a9
--- /dev/null
+++ b/MiniSpace.Services.Reports/MiniSpace.Services.Reports.sln
@@ -0,0 +1,48 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{62C95F84-EA55-4778-B3B2-A86F4CAC3251}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiniSpace.Services.Reports.Api", "src\MiniSpace.Services.Reports.Api\MiniSpace.Services.Reports.Api.csproj", "{1B494198-CB0A-49A0-95A8-4AAE613CEACA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiniSpace.Services.Reports.Application", "src\MiniSpace.Services.Reports.Application\MiniSpace.Services.Reports.Application.csproj", "{E9BDACF7-D03A-4CAF-B127-80444BB751CD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiniSpace.Services.Reports.Core", "src\MiniSpace.Services.Reports.Core\MiniSpace.Services.Reports.Core.csproj", "{C1F71C74-5021-4FC8-9EB9-7222D093ABF9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiniSpace.Services.Reports.Infrastructure", "src\MiniSpace.Services.Reports.Infrastructure\MiniSpace.Services.Reports.Infrastructure.csproj", "{F4851582-777D-473D-8A50-AB4FA57EC91A}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1B494198-CB0A-49A0-95A8-4AAE613CEACA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1B494198-CB0A-49A0-95A8-4AAE613CEACA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1B494198-CB0A-49A0-95A8-4AAE613CEACA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1B494198-CB0A-49A0-95A8-4AAE613CEACA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E9BDACF7-D03A-4CAF-B127-80444BB751CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E9BDACF7-D03A-4CAF-B127-80444BB751CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E9BDACF7-D03A-4CAF-B127-80444BB751CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E9BDACF7-D03A-4CAF-B127-80444BB751CD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C1F71C74-5021-4FC8-9EB9-7222D093ABF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C1F71C74-5021-4FC8-9EB9-7222D093ABF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C1F71C74-5021-4FC8-9EB9-7222D093ABF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C1F71C74-5021-4FC8-9EB9-7222D093ABF9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F4851582-777D-473D-8A50-AB4FA57EC91A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F4851582-777D-473D-8A50-AB4FA57EC91A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F4851582-777D-473D-8A50-AB4FA57EC91A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F4851582-777D-473D-8A50-AB4FA57EC91A}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {1B494198-CB0A-49A0-95A8-4AAE613CEACA} = {62C95F84-EA55-4778-B3B2-A86F4CAC3251}
+ {E9BDACF7-D03A-4CAF-B127-80444BB751CD} = {62C95F84-EA55-4778-B3B2-A86F4CAC3251}
+ {C1F71C74-5021-4FC8-9EB9-7222D093ABF9} = {62C95F84-EA55-4778-B3B2-A86F4CAC3251}
+ {F4851582-777D-473D-8A50-AB4FA57EC91A} = {62C95F84-EA55-4778-B3B2-A86F4CAC3251}
+ EndGlobalSection
+EndGlobal
diff --git a/MiniSpace.Services.Reports/scripts/build.sh b/MiniSpace.Services.Reports/scripts/build.sh
new file mode 100644
index 000000000..3affad0eb
--- /dev/null
+++ b/MiniSpace.Services.Reports/scripts/build.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+dotnet build -c release
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/scripts/dockerize-tag-push.sh b/MiniSpace.Services.Reports/scripts/dockerize-tag-push.sh
new file mode 100644
index 000000000..45d628c19
--- /dev/null
+++ b/MiniSpace.Services.Reports/scripts/dockerize-tag-push.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+export ASPNETCORE_ENVIRONMENT=docker
+
+cd ..
+
+docker build -t minispace.services.reports:latest .
+
+docker tag minispace.services.reports:latest adrianvsaint/minispace.services.reports:latest
+
+docker push adrianvsaint/minispace.services.reports:latest
diff --git a/MiniSpace.Services.Reports/scripts/start.sh b/MiniSpace.Services.Reports/scripts/start.sh
new file mode 100644
index 000000000..59c777d46
--- /dev/null
+++ b/MiniSpace.Services.Reports/scripts/start.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+export ASPNETCORE_ENVIRONMENT=local
+cd ../src/MiniSpace.Services.Reports.Api
+dotnet run
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/scripts/test.sh b/MiniSpace.Services.Reports/scripts/test.sh
new file mode 100644
index 000000000..6046c35a0
--- /dev/null
+++ b/MiniSpace.Services.Reports/scripts/test.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+dotnet test
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/MiniSpace.Services.Reports.Api.csproj b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/MiniSpace.Services.Reports.Api.csproj
new file mode 100644
index 000000000..9ae62c447
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/MiniSpace.Services.Reports.Api.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net8.0
+ disable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/Program.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/Program.cs
new file mode 100644
index 000000000..a1ee78d14
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/Program.cs
@@ -0,0 +1,53 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Convey;
+using Convey.Logging;
+using Convey.Types;
+using Convey.WebApi;
+using Convey.WebApi.CQRS;
+using Microsoft.AspNetCore;
+using MiniSpace.Services.Reports.Application;
+using MiniSpace.Services.Reports.Application.Commands;
+using MiniSpace.Services.Reports.Application.DTO;
+using MiniSpace.Services.Reports.Application.Queries;
+using MiniSpace.Services.Reports.Application.Services;
+using MiniSpace.Services.Reports.Core.Wrappers;
+using MiniSpace.Services.Reports.Infrastructure;
+
+namespace MiniSpace.Services.Reports.Api
+{
+ public class Program
+ {
+ public static async Task Main(string[] args)
+ => await WebHost.CreateDefaultBuilder(args)
+ .ConfigureServices(services => services
+ .AddConvey()
+ .AddWebApi()
+ .AddApplication()
+ .AddInfrastructure()
+ .Build())
+ .Configure(app => app
+ .UseInfrastructure()
+ .UseEndpoints(endpoints => endpoints
+ .Post("reports/search", async (cmd, ctx) =>
+ {
+ var pagedResult = await ctx.RequestServices.GetService().BrowseReportsAsync(cmd);
+ await ctx.Response.WriteJsonAsync(pagedResult);
+ })
+ )
+ .UseDispatcherEndpoints(endpoints => endpoints
+ .Get("", ctx => ctx.Response.WriteAsync(ctx.RequestServices.GetService().Name))
+ .Post("reports", afterDispatch: (cmd, ctx)
+ => ctx.Response.Created($"reports/{cmd.ReportId}"))
+ .Delete("reports/{reportId}")
+ .Post("reports/{reportId}/cancel")
+ .Post("reports/{reportId}/start-review")
+ .Post("reports/{reportId}/resolve")
+ .Post("reports/{reportId}/reject")
+ .Get>>("reports/students/{studentId}")
+ ))
+ .UseLogging()
+ .Build()
+ .RunAsync();
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/Properties/launchSettings.json b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/Properties/launchSettings.json
new file mode 100644
index 000000000..b1b8edd5b
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/Properties/launchSettings.json
@@ -0,0 +1,26 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:5005"
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": false,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "local"
+ }
+ },
+ "MiniSpace.Services.Reports": {
+ "commandName": "Project",
+ "launchBrowser": false,
+ "applicationUrl": "http://localhost:5005",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "local"
+ }
+ }
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/appsettings.Development.json b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/appsettings.Development.json
new file mode 100644
index 000000000..2c63c0851
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/appsettings.Development.json
@@ -0,0 +1,2 @@
+{
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/appsettings.docker.json b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/appsettings.docker.json
new file mode 100644
index 000000000..2d194e418
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/appsettings.docker.json
@@ -0,0 +1,153 @@
+{
+ "app": {
+ "name": "MiniSpace reports Service",
+ "version": "1"
+ },
+ "consul": {
+ "enabled": true,
+ "url": "http://consul:8500",
+ "service": "reports-service",
+ "address": "reports-service",
+ "port": "80",
+ "pingEnabled": true,
+ "pingEndpoint": "ping",
+ "pingInterval": 3,
+ "removeAfterInterval": 3
+ },
+ "fabio": {
+ "enabled": true,
+ "url": "http://fabio:9999",
+ "service": "reports-service"
+ },
+ "httpClient": {
+ "type": "fabio",
+ "retries": 3,
+ "services": {}
+ },
+ "jwt": {
+ "certificate": {
+ "location": "",
+ "password": "",
+ "rawData": ""
+ },
+ "issuerSigningKey": "eiquief5phee9pazo0Faegaez9gohThailiur5woy2befiech1oarai4aiLi6ahVecah3ie9Aiz6Peij",
+ "expiryMinutes": 60,
+ "issuer": "minispace",
+ "validateAudience": false,
+ "validateIssuer": false,
+ "validateLifetime": true,
+ "allowAnonymousEndpoints": ["/sign-in", "/sign-up"]
+ },
+ "logger": {
+ "console": {
+ "enabled": true
+ },
+ "elk": {
+ "enabled": false,
+ "url": "http://elk:9200"
+ },
+ "file": {
+ "enabled": false,
+ "path": "logs/logs.txt",
+ "interval": "day"
+ },
+ "seq": {
+ "enabled": true,
+ "url": "http://seq:5341",
+ "apiKey": "secret"
+ }
+ },
+ "jaeger": {
+ "enabled": true,
+ "serviceName": "reports",
+ "udpHost": "jaeger",
+ "udpPort": 6831,
+ "maxPacketSize": 0,
+ "sampler": "const",
+ "excludePaths": ["/", "/ping", "/metrics"]
+ },
+ "metrics": {
+ "enabled": true,
+ "influxEnabled": false,
+ "prometheusEnabled": true,
+ "influxUrl": "http://influx:8086",
+ "database": "minispace",
+ "env": "docker",
+ "interval": 5
+ },
+ "mongo": {
+ "connectionString": "mongodb+srv://minispace-user:9vd6IxYWUuuqhzEH@cluster0.mmhq4pe.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0",
+ "database": "reports-service",
+ "seed": false
+ },
+ "rabbitMq": {
+ "connectionName": "reports-service",
+ "retries": 3,
+ "retryInterval": 2,
+ "conventionsCasing": "snakeCase",
+ "logger": {
+ "enabled": true
+ },
+ "username": "guest",
+ "password": "guest",
+ "virtualHost": "/",
+ "port": 5672,
+ "hostnames": [
+ "rabbitmq"
+ ],
+ "requestedConnectionTimeout": "00:00:30",
+ "requestedHeartbeat": "00:01:00",
+ "socketReadTimeout": "00:00:30",
+ "socketWriteTimeout": "00:00:30",
+ "continuationTimeout": "00:00:20",
+ "handshakeContinuationTimeout": "00:00:10",
+ "networkRecoveryInterval": "00:00:05",
+ "exchange": {
+ "declare": true,
+ "durable": true,
+ "autoDelete": false,
+ "type": "topic",
+ "name": "reports"
+ },
+ "queue": {
+ "declare": true,
+ "durable": true,
+ "exclusive": false,
+ "autoDelete": false,
+ "template": "reports-service/{{exchange}}.{{message}}"
+ },
+ "context": {
+ "enabled": true,
+ "header": "message_context"
+ },
+ "spanContextHeader": "span_context"
+ },
+ "redis": {
+ "connectionString": "redis",
+ "instance": "reports:"
+ },
+ "swagger": {
+ "enabled": true,
+ "reDocEnabled": false,
+ "name": "v1",
+ "title": "API",
+ "version": "v1",
+ "routePrefix": "docs",
+ "includeSecurity": true
+ },
+ "vault": {
+ "enabled": false,
+ "url": "http://vault:8200",
+ "kv": {
+ "enabled": false
+ },
+ "pki": {
+ "enabled": false
+ },
+ "lease": {
+ "mongo": {
+ "enabled": false
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/appsettings.json b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/appsettings.json
new file mode 100644
index 000000000..10f68b8c8
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/appsettings.local.json b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/appsettings.local.json
new file mode 100644
index 000000000..6215f26c4
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Api/appsettings.local.json
@@ -0,0 +1,199 @@
+{
+ "app": {
+ "name": "MiniSpace Reports Service",
+ "service": "reports-service",
+ "version": "1"
+ },
+ "consul": {
+ "enabled": true,
+ "url": "http://localhost:8500",
+ "service": "reports-service",
+ "address": "docker.for.win.localhost",
+ "port": "5005",
+ "pingEnabled": false,
+ "pingEndpoint": "ping",
+ "pingInterval": 3,
+ "removeAfterInterval": 3
+ },
+ "fabio": {
+ "enabled": true,
+ "url": "http://localhost:9999",
+ "service": "reports-service"
+ },
+ "httpClient": {
+ "type": "fabio",
+ "retries": 3,
+ "services": {},
+ "requestMasking": {
+ "enabled": true,
+ "maskTemplate": "*****"
+ }
+ },
+ "jwt": {
+ "certificate": {
+ "location": "certs/localhost.pfx",
+ "password": "test",
+ "rawData": ""
+ },
+ "issuerSigningKey": "eiquief5phee9pazo0Faegaez9gohThailiur5woy2befiech1oarai4aiLi6ahVecah3ie9Aiz6Peij",
+ "expiryMinutes": 60,
+ "issuer": "minispace",
+ "validateAudience": false,
+ "validateIssuer": false,
+ "validateLifetime": false,
+ "allowAnonymousEndpoints": ["/sign-in", "/sign-up"]
+ },
+ "logger": {
+ "level": "information",
+ "excludePaths": ["/", "/ping", "/metrics"],
+ "excludeProperties": [
+ "api_key",
+ "access_key",
+ "ApiKey",
+ "ApiSecret",
+ "ClientId",
+ "ClientSecret",
+ "ConnectionString",
+ "Password",
+ "Email",
+ "Login",
+ "Secret",
+ "Token"
+ ],
+ "console": {
+ "enabled": true
+ },
+ "elk": {
+ "enabled": false,
+ "url": "http://localhost:9200"
+ },
+ "file": {
+ "enabled": true,
+ "path": "logs/logs.txt",
+ "interval": "day"
+ },
+ "seq": {
+ "enabled": true,
+ "url": "http://localhost:5341",
+ "apiKey": "secret"
+ },
+ "tags": {}
+ },
+ "jaeger": {
+ "enabled": true,
+ "serviceName": "reports",
+ "udpHost": "localhost",
+ "udpPort": 6831,
+ "maxPacketSize": 0,
+ "sampler": "const",
+ "excludePaths": ["/", "/ping", "/metrics"]
+ },
+ "metrics": {
+ "enabled": true,
+ "influxEnabled": false,
+ "prometheusEnabled": true,
+ "influxUrl": "http://localhost:8086",
+ "database": "minispace",
+ "env": "local",
+ "interval": 5
+ },
+ "mongo": {
+ "connectionString": "mongodb+srv://minispace-user:9vd6IxYWUuuqhzEH@cluster0.mmhq4pe.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0",
+ "database": "reports-service",
+ "seed": false
+ },
+ "outbox": {
+ "enabled": false,
+ "type": "sequential",
+ "expiry": 3600,
+ "intervalMilliseconds": 2000,
+ "inboxCollection": "inbox",
+ "outboxCollection": "outbox",
+ "disableTransactions": true
+ },
+ "rabbitMq": {
+ "connectionName": "reports-service",
+ "retries": 3,
+ "retryInterval": 2,
+ "conventionsCasing": "snakeCase",
+ "logger": {
+ "enabled": true
+ },
+ "username": "guest",
+ "password": "guest",
+ "virtualHost": "/",
+ "port": 5672,
+ "hostnames": [
+ "localhost"
+ ],
+ "requestedConnectionTimeout": "00:00:30",
+ "requestedHeartbeat": "00:01:00",
+ "socketReadTimeout": "00:00:30",
+ "socketWriteTimeout": "00:00:30",
+ "continuationTimeout": "00:00:20",
+ "handshakeContinuationTimeout": "00:00:10",
+ "networkRecoveryInterval": "00:00:05",
+ "exchange": {
+ "declare": true,
+ "durable": true,
+ "autoDelete": false,
+ "type": "topic",
+ "name": "reports"
+ },
+ "queue": {
+ "declare": true,
+ "durable": true,
+ "exclusive": false,
+ "autoDelete": false,
+ "template": "reports-service/{{exchange}}.{{message}}"
+ },
+ "context": {
+ "enabled": true,
+ "header": "message_context"
+ },
+ "spanContextHeader": "span_context"
+ },
+ "redis": {
+ "connectionString": "localhost",
+ "instance": "reports:"
+ },
+ "swagger": {
+ "enabled": true,
+ "reDocEnabled": false,
+ "name": "v1",
+ "title": "API",
+ "version": "v1",
+ "routePrefix": "docs",
+ "includeSecurity": true
+ },
+ "vault": {
+ "enabled": true,
+ "url": "http://localhost:8200",
+ "authType": "token",
+ "token": "secret",
+ "username": "user",
+ "password": "secret",
+ "kv": {
+ "enabled": true,
+ "engineVersion": 2,
+ "mountPoint": "kv",
+ "path": "reports-service/settings"
+ },
+ "pki": {
+ "enabled": true,
+ "roleName": "reports-service",
+ "commonName": "reports-service.minispace.io"
+ },
+ "lease": {
+ "mongo": {
+ "type": "database",
+ "roleName": "reports-service",
+ "enabled": true,
+ "autoRenewal": true,
+ "templates": {
+ "connectionString": "mongodb://{{username}}:{{password}}@localhost:27017"
+ }
+ }
+ }
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/CancelReport.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/CancelReport.cs
new file mode 100644
index 000000000..a054fb34c
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/CancelReport.cs
@@ -0,0 +1,14 @@
+using Convey.CQRS.Commands;
+
+namespace MiniSpace.Services.Reports.Application.Commands
+{
+ public class CancelReport: ICommand
+ {
+ public Guid ReportId { get; }
+
+ public CancelReport(Guid reportId)
+ {
+ ReportId = reportId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/CreateReport.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/CreateReport.cs
new file mode 100644
index 000000000..5e7759caf
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/CreateReport.cs
@@ -0,0 +1,28 @@
+using Convey.CQRS.Commands;
+using MiniSpace.Services.Reports.Core.Entities;
+
+namespace MiniSpace.Services.Reports.Application.Commands
+{
+ public class CreateReport: ICommand
+ {
+ public Guid ReportId { get; }
+ public Guid IssuerId { get; set; }
+ public Guid TargetId { get; set; }
+ public Guid TargetOwnerId { get; set; }
+ public string ContextType { get; set; }
+ public string Category { get; set; }
+ public string Reason { get; private set; }
+
+ public CreateReport(Guid reportId, Guid issuerId, Guid targetId, Guid targetOwnerId, string contextType,
+ string category, string reason)
+ {
+ ReportId = reportId == Guid.Empty ? Guid.NewGuid() : reportId;
+ IssuerId = issuerId;
+ TargetId = targetId;
+ TargetOwnerId = targetOwnerId;
+ ContextType = contextType;
+ Category = category;
+ Reason = reason;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/DeleteReport.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/DeleteReport.cs
new file mode 100644
index 000000000..9d028fb6a
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/DeleteReport.cs
@@ -0,0 +1,9 @@
+using Convey.CQRS.Commands;
+
+namespace MiniSpace.Services.Reports.Application.Commands
+{
+ public class DeleteReport: ICommand
+ {
+ public Guid ReportId { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/Handlers/CancelReportHandler.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/Handlers/CancelReportHandler.cs
new file mode 100644
index 000000000..3a19f2f30
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/Handlers/CancelReportHandler.cs
@@ -0,0 +1,44 @@
+using Convey.CQRS.Commands;
+using MiniSpace.Services.Reports.Application.Events;
+using MiniSpace.Services.Reports.Application.Exceptions;
+using MiniSpace.Services.Reports.Application.Services;
+using MiniSpace.Services.Reports.Core.Repositories;
+
+namespace MiniSpace.Services.Reports.Application.Commands.Handlers
+{
+ public class CancelReportHandler : ICommandHandler
+ {
+ private readonly IReportRepository _reportRepository;
+ private readonly IAppContext _appContext;
+ private readonly IDateTimeProvider _dateTimeProvider;
+ private readonly IMessageBroker _messageBroker;
+
+ public CancelReportHandler(IReportRepository reportRepository, IAppContext appContext,
+ IDateTimeProvider dateTimeProvider, IMessageBroker messageBroker)
+ {
+ _reportRepository = reportRepository;
+ _appContext = appContext;
+ _dateTimeProvider = dateTimeProvider;
+ _messageBroker = messageBroker;
+ }
+
+ public async Task HandleAsync(CancelReport command, CancellationToken cancellationToken)
+ {
+ var report = await _reportRepository.GetAsync(command.ReportId);
+ if (report is null)
+ {
+ throw new ReportNotFoundException(command.ReportId);
+ }
+
+ var identity = _appContext.Identity;
+ if (identity.IsAuthenticated && identity.Id != report.IssuerId)
+ {
+ throw new UnauthorizedReportAccessAttemptException(report.Id, identity.Id);
+ }
+
+ report.Cancel(_dateTimeProvider.Now);
+ await _reportRepository.UpdateAsync(report);
+ await _messageBroker.PublishAsync(new ReportCancelled(report.Id));
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/Handlers/CreateReportHandler.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/Handlers/CreateReportHandler.cs
new file mode 100644
index 000000000..2f8cbeda7
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/Handlers/CreateReportHandler.cs
@@ -0,0 +1,48 @@
+using Convey.CQRS.Commands;
+using MiniSpace.Services.Reports.Application.Events;
+using MiniSpace.Services.Reports.Application.Exceptions;
+using MiniSpace.Services.Reports.Application.Services;
+using MiniSpace.Services.Reports.Core.Entities;
+using MiniSpace.Services.Reports.Core.Repositories;
+
+namespace MiniSpace.Services.Reports.Application.Commands.Handlers
+{
+ public class CreateReportHandler : ICommandHandler
+ {
+ private readonly IReportRepository _reportRepository;
+ private readonly IReportValidator _reportValidator;
+ private readonly IAppContext _appContext;
+ private readonly IDateTimeProvider _dateTimeProvider;
+ private readonly IMessageBroker _messageBroker;
+
+ public CreateReportHandler(IReportRepository reportRepository, IReportValidator reportValidator,
+ IAppContext appContext, IDateTimeProvider dateTimeProvider, IMessageBroker messageBroker)
+ {
+ _reportRepository = reportRepository;
+ _reportValidator = reportValidator;
+ _appContext = appContext;
+ _dateTimeProvider = dateTimeProvider;
+ _messageBroker = messageBroker;
+ }
+
+ public async Task HandleAsync(CreateReport command, CancellationToken cancellationToken)
+ {
+ var identity = _appContext.Identity;
+ if (identity.IsAuthenticated && identity.Id != command.IssuerId)
+ {
+ throw new UnauthorizedReportCreationAttemptException(identity.Id, command.IssuerId);
+ }
+
+ var contextType = _reportValidator.ParseContextType(command.ContextType);
+ var category = _reportValidator.ParseCategory(command.Category);
+ _reportValidator.ValidateReason(command.Reason);
+ var activeStudentReports = await _reportRepository.GetStudentActiveReportsAsync(command.IssuerId);
+ _reportValidator.ValidateActiveReports(activeStudentReports.Count());
+
+ var report = Report.Create(command.ReportId, command.IssuerId, command.TargetId, command.TargetOwnerId,
+ contextType, category, command.Reason, _dateTimeProvider.Now);
+ await _reportRepository.AddAsync(report);
+ await _messageBroker.PublishAsync(new ReportCreated(report.Id));
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/Handlers/DeleteReportHandler.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/Handlers/DeleteReportHandler.cs
new file mode 100644
index 000000000..705d13440
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/Handlers/DeleteReportHandler.cs
@@ -0,0 +1,41 @@
+using Convey.CQRS.Commands;
+using MiniSpace.Services.Reports.Application.Events;
+using MiniSpace.Services.Reports.Application.Exceptions;
+using MiniSpace.Services.Reports.Application.Services;
+using MiniSpace.Services.Reports.Core.Repositories;
+
+namespace MiniSpace.Services.Reports.Application.Commands.Handlers
+{
+ public class DeleteReportHandler : ICommandHandler
+ {
+ private readonly IReportRepository _reportRepository;
+ private readonly IAppContext _appContext;
+ private readonly IMessageBroker _messageBroker;
+
+ public DeleteReportHandler(IReportRepository reportRepository, IAppContext appContext,
+ IMessageBroker messageBroker)
+ {
+ _reportRepository = reportRepository;
+ _appContext = appContext;
+ _messageBroker = messageBroker;
+ }
+
+ public async Task HandleAsync(DeleteReport command, CancellationToken cancellationToken)
+ {
+ var report = await _reportRepository.GetAsync(command.ReportId);
+ if (report is null)
+ {
+ throw new ReportNotFoundException(command.ReportId);
+ }
+
+ var identity = _appContext.Identity;
+ if (identity.IsAuthenticated && !identity.IsAdmin)
+ {
+ throw new UnauthorizedReportAccessAttemptException(report.Id, identity.Id);
+ }
+
+ await _reportRepository.DeleteAsync(report.Id);
+ await _messageBroker.PublishAsync(new ReportDeleted(report.Id));
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/Handlers/RejectReportHandler.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/Handlers/RejectReportHandler.cs
new file mode 100644
index 000000000..c54f4e73b
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/Handlers/RejectReportHandler.cs
@@ -0,0 +1,44 @@
+using Convey.CQRS.Commands;
+using MiniSpace.Services.Reports.Application.Events;
+using MiniSpace.Services.Reports.Application.Exceptions;
+using MiniSpace.Services.Reports.Application.Services;
+using MiniSpace.Services.Reports.Core.Repositories;
+
+namespace MiniSpace.Services.Reports.Application.Commands.Handlers
+{
+ public class RejectReportHandler : ICommandHandler
+ {
+ private readonly IReportRepository _reportRepository;
+ private readonly IAppContext _appContext;
+ private readonly IDateTimeProvider _dateTimeProvider;
+ private readonly IMessageBroker _messageBroker;
+
+ public RejectReportHandler(IReportRepository reportRepository, IAppContext appContext,
+ IDateTimeProvider dateTimeProvider, IMessageBroker messageBroker)
+ {
+ _reportRepository = reportRepository;
+ _appContext = appContext;
+ _dateTimeProvider = dateTimeProvider;
+ _messageBroker = messageBroker;
+ }
+
+ public async Task HandleAsync(RejectReport command, CancellationToken cancellationToken)
+ {
+ var report = await _reportRepository.GetAsync(command.ReportId);
+ if (report is null)
+ {
+ throw new ReportNotFoundException(command.ReportId);
+ }
+
+ var identity = _appContext.Identity;
+ if (identity.IsAuthenticated && identity.Id != report.ReviewerId)
+ {
+ throw new UnauthorizedReportAccessAttemptException(report.Id, identity.Id);
+ }
+
+ report.Reject(_dateTimeProvider.Now);
+ await _reportRepository.UpdateAsync(report);
+ await _messageBroker.PublishAsync(new ReportResolved(report.Id));
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/Handlers/ResolveReportHandler.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/Handlers/ResolveReportHandler.cs
new file mode 100644
index 000000000..fc6d804b2
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/Handlers/ResolveReportHandler.cs
@@ -0,0 +1,44 @@
+using Convey.CQRS.Commands;
+using MiniSpace.Services.Reports.Application.Events;
+using MiniSpace.Services.Reports.Application.Exceptions;
+using MiniSpace.Services.Reports.Application.Services;
+using MiniSpace.Services.Reports.Core.Repositories;
+
+namespace MiniSpace.Services.Reports.Application.Commands.Handlers
+{
+ public class ResolveReportHandler : ICommandHandler
+ {
+ private readonly IReportRepository _reportRepository;
+ private readonly IAppContext _appContext;
+ private readonly IDateTimeProvider _dateTimeProvider;
+ private readonly IMessageBroker _messageBroker;
+
+ public ResolveReportHandler(IReportRepository reportRepository, IAppContext appContext,
+ IDateTimeProvider dateTimeProvider, IMessageBroker messageBroker)
+ {
+ _reportRepository = reportRepository;
+ _appContext = appContext;
+ _dateTimeProvider = dateTimeProvider;
+ _messageBroker = messageBroker;
+ }
+
+ public async Task HandleAsync(ResolveReport command, CancellationToken cancellationToken)
+ {
+ var report = await _reportRepository.GetAsync(command.ReportId);
+ if (report is null)
+ {
+ throw new ReportNotFoundException(command.ReportId);
+ }
+
+ var identity = _appContext.Identity;
+ if (identity.IsAuthenticated && identity.Id != report.ReviewerId)
+ {
+ throw new UnauthorizedReportAccessAttemptException(report.Id, identity.Id);
+ }
+
+ report.Resolve(_dateTimeProvider.Now);
+ await _reportRepository.UpdateAsync(report);
+ await _messageBroker.PublishAsync(new ReportResolved(report.Id));
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/Handlers/StartReportReviewHandler.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/Handlers/StartReportReviewHandler.cs
new file mode 100644
index 000000000..5d2ec7690
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/Handlers/StartReportReviewHandler.cs
@@ -0,0 +1,44 @@
+using Convey.CQRS.Commands;
+using MiniSpace.Services.Reports.Application.Events;
+using MiniSpace.Services.Reports.Application.Exceptions;
+using MiniSpace.Services.Reports.Application.Services;
+using MiniSpace.Services.Reports.Core.Repositories;
+
+namespace MiniSpace.Services.Reports.Application.Commands.Handlers
+{
+ public class StartReportReviewHandler : ICommandHandler
+ {
+ private readonly IReportRepository _reportRepository;
+ private readonly IAppContext _appContext;
+ private readonly IDateTimeProvider _dateTimeProvider;
+ private readonly IMessageBroker _messageBroker;
+
+ public StartReportReviewHandler(IReportRepository reportRepository, IAppContext appContext,
+ IDateTimeProvider dateTimeProvider, IMessageBroker messageBroker)
+ {
+ _reportRepository = reportRepository;
+ _appContext = appContext;
+ _dateTimeProvider = dateTimeProvider;
+ _messageBroker = messageBroker;
+ }
+
+ public async Task HandleAsync(StartReportReview command, CancellationToken cancellationToken)
+ {
+ var report = await _reportRepository.GetAsync(command.ReportId);
+ if (report is null)
+ {
+ throw new ReportNotFoundException(command.ReportId);
+ }
+
+ var identity = _appContext.Identity;
+ if (identity.IsAuthenticated && !identity.IsAdmin)
+ {
+ throw new UnauthorizedReportAccessAttemptException(report.Id, identity.Id);
+ }
+
+ report.StartReview(command.ReviewerId, _dateTimeProvider.Now);
+ await _reportRepository.UpdateAsync(report);
+ await _messageBroker.PublishAsync(new ReportReviewStarted(report.Id, command.ReviewerId));
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/RejectReport.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/RejectReport.cs
new file mode 100644
index 000000000..e5b47ca22
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/RejectReport.cs
@@ -0,0 +1,14 @@
+using Convey.CQRS.Commands;
+
+namespace MiniSpace.Services.Reports.Application.Commands
+{
+ public class RejectReport : ICommand
+ {
+ public Guid ReportId { get; }
+
+ public RejectReport(Guid reportId)
+ {
+ ReportId = reportId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/ResolveReport.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/ResolveReport.cs
new file mode 100644
index 000000000..304ac20f6
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/ResolveReport.cs
@@ -0,0 +1,14 @@
+using Convey.CQRS.Commands;
+
+namespace MiniSpace.Services.Reports.Application.Commands
+{
+ public class ResolveReport : ICommand
+ {
+ public Guid ReportId { get; }
+
+ public ResolveReport(Guid reportId)
+ {
+ ReportId = reportId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/SearchReports.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/SearchReports.cs
new file mode 100644
index 000000000..388e493e9
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/SearchReports.cs
@@ -0,0 +1,13 @@
+using Convey.CQRS.Commands;
+using MiniSpace.Services.Reports.Application.DTO;
+
+namespace MiniSpace.Services.Reports.Application.Commands
+{
+ public class SearchReports : ICommand
+ {
+ public IEnumerable ContextTypes { get; set; }
+ public IEnumerable States { get; set; }
+ public Guid ReviewerId { get; set; }
+ public PageableDto Pageable { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/StartReportReview.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/StartReportReview.cs
new file mode 100644
index 000000000..e585861a4
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Commands/StartReportReview.cs
@@ -0,0 +1,16 @@
+using Convey.CQRS.Commands;
+
+namespace MiniSpace.Services.Reports.Application.Commands
+{
+ public class StartReportReview: ICommand
+ {
+ public Guid ReportId { get; }
+ public Guid ReviewerId { get; set; }
+
+ public StartReportReview(Guid reportId, Guid reviewerId)
+ {
+ ReportId = reportId;
+ ReviewerId = reviewerId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/ContractAttribute.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/ContractAttribute.cs
new file mode 100644
index 000000000..1f8f0e698
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/ContractAttribute.cs
@@ -0,0 +1,7 @@
+namespace MiniSpace.Services.Reports.Application
+{
+ public class ContractAttribute : Attribute
+ {
+
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/DTO/PageableDto.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/DTO/PageableDto.cs
new file mode 100644
index 000000000..76c906d29
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/DTO/PageableDto.cs
@@ -0,0 +1,9 @@
+namespace MiniSpace.Services.Reports.Application.DTO
+{
+ public class PageableDto
+ {
+ public int Page { get; set; }
+ public int Size { get; set; }
+ public SortDto Sort { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/DTO/ReportDto.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/DTO/ReportDto.cs
new file mode 100644
index 000000000..4e0a1198f
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/DTO/ReportDto.cs
@@ -0,0 +1,39 @@
+using MiniSpace.Services.Reports.Core.Entities;
+
+namespace MiniSpace.Services.Reports.Application.DTO
+{
+ public class ReportDto
+ {
+ public Guid Id { get; set; }
+ public Guid IssuerId { get; set; }
+ public Guid TargetId { get; set; }
+ public Guid TargetOwnerId { get; set; }
+ public string ContextType { get; set; }
+ public string Category { get; set; }
+ public string Reason { get; set; }
+ public string State { get; set; }
+ public DateTime CreatedAt { get; set; }
+ public DateTime UpdatedAt { get; set; }
+ public Guid? ReviewerId { get; set; }
+
+ public ReportDto()
+ {
+
+ }
+
+ public ReportDto(Report report)
+ {
+ Id = report.Id;
+ IssuerId = report.IssuerId;
+ TargetId = report.TargetId;
+ TargetOwnerId = report.TargetOwnerId;
+ ContextType = report.ContextType.ToString();
+ Category = report.Category.ToString();
+ Reason = report.Reason;
+ State = report.State.ToString();
+ CreatedAt = report.CreatedAt;
+ UpdatedAt = report.UpdatedAt;
+ ReviewerId = report.ReviewerId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/DTO/SortDto.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/DTO/SortDto.cs
new file mode 100644
index 000000000..c6a5d9680
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/DTO/SortDto.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+namespace MiniSpace.Services.Reports.Application.DTO
+{
+ public class SortDto
+ {
+ public IEnumerable SortBy { get; set; }
+ public string Direction { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/CommentCreated.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/CommentCreated.cs
new file mode 100644
index 000000000..c10c6a5e1
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/CommentCreated.cs
@@ -0,0 +1,16 @@
+using Convey.CQRS.Events;
+using Convey.MessageBrokers;
+
+namespace MiniSpace.Services.Reports.Application.Events.External
+{
+ [Message("comments")]
+ public class CommentCreated : IEvent
+ {
+ public Guid CommentId { get; }
+
+ public CommentCreated(Guid commentId)
+ {
+ CommentId = commentId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/EventCreated.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/EventCreated.cs
new file mode 100644
index 000000000..0945bf7c1
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/EventCreated.cs
@@ -0,0 +1,16 @@
+using Convey.CQRS.Events;
+using Convey.MessageBrokers;
+
+namespace MiniSpace.Services.Reports.Application.Events.External
+{
+ [Message("events")]
+ public class EventCreated : IEvent
+ {
+ public Guid EventId { get; }
+
+ public EventCreated(Guid eventId)
+ {
+ EventId = eventId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/Handlers/CommentCreatedHandler.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/Handlers/CommentCreatedHandler.cs
new file mode 100644
index 000000000..fcf2834f9
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/Handlers/CommentCreatedHandler.cs
@@ -0,0 +1,27 @@
+using Convey.CQRS.Events;
+using MiniSpace.Services.Reports.Application.Exceptions;
+using MiniSpace.Services.Reports.Core.Entities;
+using MiniSpace.Services.Reports.Core.Repositories;
+
+namespace MiniSpace.Services.Reports.Application.Events.External.Handlers
+{
+ public class CommentCreatedHandler : IEventHandler
+ {
+ private readonly ICommentRepository _commentRepository;
+
+ public CommentCreatedHandler(ICommentRepository commentRepository)
+ {
+ _commentRepository = commentRepository;
+ }
+
+ public async Task HandleAsync(CommentCreated @event, CancellationToken cancellationToken)
+ {
+ if (await _commentRepository.ExistsAsync(@event.CommentId))
+ {
+ throw new CommentAlreadyAddedException(@event.CommentId);
+ }
+
+ await _commentRepository.AddAsync(new Comment(@event.CommentId));
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/Handlers/EventCreatedHandler.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/Handlers/EventCreatedHandler.cs
new file mode 100644
index 000000000..2ca301504
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/Handlers/EventCreatedHandler.cs
@@ -0,0 +1,27 @@
+using Convey.CQRS.Events;
+using MiniSpace.Services.Reports.Application.Exceptions;
+using MiniSpace.Services.Reports.Core.Entities;
+using MiniSpace.Services.Reports.Core.Repositories;
+
+namespace MiniSpace.Services.Reports.Application.Events.External.Handlers
+{
+ public class EventCreatedHandler : IEventHandler
+ {
+ private readonly IEventRepository _eventRepository;
+
+ public EventCreatedHandler(IEventRepository eventRepository)
+ {
+ _eventRepository = eventRepository;
+ }
+
+ public async Task HandleAsync(EventCreated @event, CancellationToken cancellationToken = default)
+ {
+ if (await _eventRepository.ExistsAsync(@event.EventId))
+ {
+ throw new EventAlreadyAddedException(@event.EventId);
+ }
+
+ await _eventRepository.AddAsync(new Event(@event.EventId));
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/Handlers/PostCreatedHandler.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/Handlers/PostCreatedHandler.cs
new file mode 100644
index 000000000..e6e793880
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/Handlers/PostCreatedHandler.cs
@@ -0,0 +1,27 @@
+using Convey.CQRS.Events;
+using MiniSpace.Services.Reports.Application.Exceptions;
+using MiniSpace.Services.Reports.Core.Entities;
+using MiniSpace.Services.Reports.Core.Repositories;
+
+namespace MiniSpace.Services.Reports.Application.Events.External.Handlers
+{
+ public class PostCreatedHandler : IEventHandler
+ {
+ private readonly IPostRepository _postRepository;
+
+ public PostCreatedHandler(IPostRepository postRepository)
+ {
+ _postRepository = postRepository;
+ }
+
+ public async Task HandleAsync(PostCreated @event, CancellationToken cancellationToken)
+ {
+ if (await _postRepository.ExistsAsync(@event.PostId))
+ {
+ throw new PostAlreadyAddedException(@event.PostId);
+ }
+
+ await _postRepository.AddAsync(new Post(@event.PostId));
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/Handlers/StudentCreatedHandler.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/Handlers/StudentCreatedHandler.cs
new file mode 100644
index 000000000..a286aded7
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/Handlers/StudentCreatedHandler.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Convey.CQRS.Events;
+using MiniSpace.Services.Reports.Application.Exceptions;
+using MiniSpace.Services.Reports.Core.Entities;
+using MiniSpace.Services.Reports.Core.Repositories;
+
+namespace MiniSpace.Services.Reports.Application.Events.External.Handlers
+{
+ public class StudentCreatedHandler : IEventHandler
+ {
+ private readonly IStudentRepository _studentRepository;
+
+ public StudentCreatedHandler(IStudentRepository studentRepository)
+ {
+ _studentRepository = studentRepository;
+ }
+
+ public async Task HandleAsync(StudentCreated @event, CancellationToken cancellationToken)
+ {
+ if (await _studentRepository.ExistsAsync(@event.StudentId))
+ {
+ throw new StudentAlreadyAddedException(@event.StudentId);
+ }
+
+ await _studentRepository.AddAsync(new Student(@event.StudentId));
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/PostCreated.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/PostCreated.cs
new file mode 100644
index 000000000..a94c9a727
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/PostCreated.cs
@@ -0,0 +1,16 @@
+using Convey.CQRS.Events;
+using Convey.MessageBrokers;
+
+namespace MiniSpace.Services.Reports.Application.Events.External
+{
+ [Message("posts")]
+ public class PostCreated : IEvent
+ {
+ public Guid PostId { get; }
+
+ public PostCreated(Guid postId)
+ {
+ PostId = postId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/StudentCreated.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/StudentCreated.cs
new file mode 100644
index 000000000..2cbad872e
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/External/StudentCreated.cs
@@ -0,0 +1,19 @@
+using System;
+using Convey.CQRS.Events;
+using Convey.MessageBrokers;
+
+namespace MiniSpace.Services.Reports.Application.Events.External
+{
+ [Message("students")]
+ public class StudentCreated : IEvent
+ {
+ public Guid StudentId { get; }
+ public string Name { get; }
+
+ public StudentCreated(Guid studentId, string name)
+ {
+ StudentId = studentId;
+ Name = name;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/ReportCancelled.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/ReportCancelled.cs
new file mode 100644
index 000000000..5724fac59
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/ReportCancelled.cs
@@ -0,0 +1,14 @@
+using Convey.CQRS.Events;
+
+namespace MiniSpace.Services.Reports.Application.Events
+{
+ public class ReportCancelled : IEvent
+ {
+ public Guid ReportId { get; }
+
+ public ReportCancelled(Guid reportId)
+ {
+ ReportId = reportId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/ReportCreated.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/ReportCreated.cs
new file mode 100644
index 000000000..f4921c1d6
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/ReportCreated.cs
@@ -0,0 +1,14 @@
+using Convey.CQRS.Events;
+
+namespace MiniSpace.Services.Reports.Application.Events
+{
+ public class ReportCreated: IEvent
+ {
+ public Guid ReportId { get; }
+
+ public ReportCreated(Guid reportId)
+ {
+ ReportId = reportId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/ReportDeleted.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/ReportDeleted.cs
new file mode 100644
index 000000000..ccf717e3c
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/ReportDeleted.cs
@@ -0,0 +1,14 @@
+using Convey.CQRS.Events;
+
+namespace MiniSpace.Services.Reports.Application.Events
+{
+ public class ReportDeleted: IEvent
+ {
+ public Guid ReportId { get; }
+
+ public ReportDeleted(Guid reportId)
+ {
+ ReportId = reportId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/ReportRejected.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/ReportRejected.cs
new file mode 100644
index 000000000..ebab2d802
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/ReportRejected.cs
@@ -0,0 +1,14 @@
+using Convey.CQRS.Events;
+
+namespace MiniSpace.Services.Reports.Application.Events
+{
+ public class ReportRejected: IEvent
+ {
+ public Guid ReportId { get; }
+
+ public ReportRejected(Guid reportId)
+ {
+ ReportId = reportId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/ReportResolved.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/ReportResolved.cs
new file mode 100644
index 000000000..53b383e86
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/ReportResolved.cs
@@ -0,0 +1,14 @@
+using Convey.CQRS.Events;
+
+namespace MiniSpace.Services.Reports.Application.Events
+{
+ public class ReportResolved: IEvent
+ {
+ public Guid ReportId { get; }
+
+ public ReportResolved(Guid reportId)
+ {
+ ReportId = reportId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/ReportReviewStarted.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/ReportReviewStarted.cs
new file mode 100644
index 000000000..5971d6c04
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Events/ReportReviewStarted.cs
@@ -0,0 +1,16 @@
+using Convey.CQRS.Events;
+
+namespace MiniSpace.Services.Reports.Application.Events
+{
+ public class ReportReviewStarted : IEvent
+ {
+ public Guid ReportId { get; }
+ public Guid ReviewerId { get; }
+
+ public ReportReviewStarted(Guid reportId, Guid reviewerId)
+ {
+ ReportId = reportId;
+ ReviewerId = reviewerId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/AppException.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/AppException.cs
new file mode 100644
index 000000000..4173a0478
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/AppException.cs
@@ -0,0 +1,11 @@
+namespace MiniSpace.Services.Reports.Application.Exceptions
+{
+ public class AppException : Exception
+ {
+ public virtual string Code { get; }
+
+ protected AppException(string message) : base(message)
+ {
+ }
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/CommentAlreadyAddedException.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/CommentAlreadyAddedException.cs
new file mode 100644
index 000000000..4bd20399e
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/CommentAlreadyAddedException.cs
@@ -0,0 +1,14 @@
+namespace MiniSpace.Services.Reports.Application.Exceptions
+{
+ public class CommentAlreadyAddedException : AppException
+ {
+ public override string Code { get; } = "comment_already_added";
+ public Guid CommentId { get; }
+
+ public CommentAlreadyAddedException(Guid commentId)
+ : base($"Comment with id: {commentId} was already added.")
+ {
+ CommentId = commentId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/EventAlreadyAddedException.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/EventAlreadyAddedException.cs
new file mode 100644
index 000000000..c525c7786
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/EventAlreadyAddedException.cs
@@ -0,0 +1,14 @@
+namespace MiniSpace.Services.Reports.Application.Exceptions
+{
+ public class EventAlreadyAddedException : AppException
+ {
+ public override string Code { get; } = "event_already_added";
+ public Guid EventId { get; }
+
+ public EventAlreadyAddedException(Guid eventId)
+ : base($"Event with id: {eventId} was already added.")
+ {
+ EventId = eventId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/InvalidContextTypeException.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/InvalidContextTypeException.cs
new file mode 100644
index 000000000..1ed00c27a
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/InvalidContextTypeException.cs
@@ -0,0 +1,14 @@
+namespace MiniSpace.Services.Reports.Application.Exceptions
+{
+ public class InvalidContextTypeException : AppException
+ {
+ public override string Code { get; } = "invalid_context_type";
+ public string ContextType { get; }
+
+ public InvalidContextTypeException(string contextType) : base($"Invalid context type: {contextType}.")
+ {
+ ContextType = contextType;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/InvalidReasonException.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/InvalidReasonException.cs
new file mode 100644
index 000000000..c60091ca6
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/InvalidReasonException.cs
@@ -0,0 +1,13 @@
+namespace MiniSpace.Services.Reports.Application.Exceptions
+{
+ public class InvalidReasonException : AppException
+ {
+ public override string Code { get; } = "invalid_reason";
+ public string Reason { get; }
+
+ public InvalidReasonException(string reason) : base($"Invalid reason: {reason}. It cannot be empty or over 1000 characters.")
+ {
+ Reason = reason;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/InvalidReportCategoryException.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/InvalidReportCategoryException.cs
new file mode 100644
index 000000000..142f32fef
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/InvalidReportCategoryException.cs
@@ -0,0 +1,13 @@
+namespace MiniSpace.Services.Reports.Application.Exceptions
+{
+ public class InvalidReportCategoryException : AppException
+ {
+ public override string Code { get; } = "invalid_report_category";
+ public string Category { get; }
+
+ public InvalidReportCategoryException(string category) : base($"Invalid report category: {category}.")
+ {
+ Category = category;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/InvalidReportState.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/InvalidReportState.cs
new file mode 100644
index 000000000..cce794f41
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/InvalidReportState.cs
@@ -0,0 +1,13 @@
+namespace MiniSpace.Services.Reports.Application.Exceptions
+{
+ public class InvalidReportStateException : AppException
+ {
+ public override string Code { get; } = "invalid_report_state";
+ public string State { get; }
+
+ public InvalidReportStateException(string state) : base($"Invalid report state: {state}.")
+ {
+ State = state;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/PostAlreadyAddedException.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/PostAlreadyAddedException.cs
new file mode 100644
index 000000000..e13dd0391
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/PostAlreadyAddedException.cs
@@ -0,0 +1,14 @@
+namespace MiniSpace.Services.Reports.Application.Exceptions
+{
+ public class PostAlreadyAddedException : AppException
+ {
+ public override string Code { get; } = "post_already_added";
+ public Guid PostId { get; }
+
+ public PostAlreadyAddedException(Guid postId)
+ : base($"Post with id: {postId} was already added.")
+ {
+ PostId = postId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/ReportNotFoundException.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/ReportNotFoundException.cs
new file mode 100644
index 000000000..c9472e2a7
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/ReportNotFoundException.cs
@@ -0,0 +1,13 @@
+namespace MiniSpace.Services.Reports.Application.Exceptions
+{
+ public class ReportNotFoundException : AppException
+ {
+ public override string Code { get; } = "report_not_found";
+ public Guid ReportId { get; }
+
+ public ReportNotFoundException(Guid reportId) : base($"Report with ID: '{reportId}' was not found.")
+ {
+ ReportId = reportId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/StudentAlreadyAddedException.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/StudentAlreadyAddedException.cs
new file mode 100644
index 000000000..bd2150767
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/StudentAlreadyAddedException.cs
@@ -0,0 +1,17 @@
+using System;
+using MiniSpace.Services.Reports.Core.Exceptions;
+
+namespace MiniSpace.Services.Reports.Application.Exceptions
+{
+ public class StudentAlreadyAddedException : AppException
+ {
+ public override string Code { get; } = "student_already_added";
+ public Guid StudentId { get; }
+
+ public StudentAlreadyAddedException(Guid studentId)
+ : base($"Student with id: {studentId} was already added.")
+ {
+ StudentId = studentId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/StudentNotFoundException.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/StudentNotFoundException.cs
new file mode 100644
index 000000000..1a2fd20ec
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/StudentNotFoundException.cs
@@ -0,0 +1,13 @@
+namespace MiniSpace.Services.Reports.Application.Exceptions
+{
+ public class StudentNotFoundException : AppException
+ {
+ public override string Code { get; } = "student_not_found";
+ public Guid StudentId { get; }
+
+ public StudentNotFoundException(Guid studentId) : base($"Student with ID: '{studentId}' was not found.")
+ {
+ StudentId = studentId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/StudentTooManyActiveReportsException.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/StudentTooManyActiveReportsException.cs
new file mode 100644
index 000000000..b40e1462d
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/StudentTooManyActiveReportsException.cs
@@ -0,0 +1,16 @@
+namespace MiniSpace.Services.Reports.Application.Exceptions
+{
+ public class StudentTooManyActiveReportsException : AppException
+ {
+ public override string Code { get; } = "student_too_many_active_reports";
+ public int ActiveReports { get; }
+ public int MaxActiveReports { get; }
+
+ public StudentTooManyActiveReportsException(int activeReports, int maxActiveReports)
+ : base($"Student has too many active reports: {activeReports}. Max allowed is: {maxActiveReports}.")
+ {
+ ActiveReports = activeReports;
+ MaxActiveReports = maxActiveReports;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/UnauthorizedReportAccessAttemptException.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/UnauthorizedReportAccessAttemptException.cs
new file mode 100644
index 000000000..0f7eb6e1a
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/UnauthorizedReportAccessAttemptException.cs
@@ -0,0 +1,16 @@
+namespace MiniSpace.Services.Reports.Application.Exceptions
+{
+ public class UnauthorizedReportAccessAttemptException : AppException
+ {
+ public override string Code => "unauthorized_report_access_attempt";
+ public Guid ReportId { get; }
+ public Guid StudentId { get; }
+
+ public UnauthorizedReportAccessAttemptException(Guid reportId, Guid studentId)
+ : base($"Unauthorized report access attempt with ID: {reportId} by student with ID: {studentId}.")
+ {
+ ReportId = reportId;
+ StudentId = studentId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/UnauthorizedReportCreationAttemptException.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/UnauthorizedReportCreationAttemptException.cs
new file mode 100644
index 000000000..b2fa891bb
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/UnauthorizedReportCreationAttemptException.cs
@@ -0,0 +1,16 @@
+namespace MiniSpace.Services.Reports.Application.Exceptions
+{
+ public class UnauthorizedReportCreationAttemptException : AppException
+ {
+ public override string Code { get; } = "unauthorized_report_creation_attempt";
+ public Guid StudentId { get; }
+ public Guid IssuerId { get; }
+
+ public UnauthorizedReportCreationAttemptException(Guid studentId, Guid issuerId)
+ : base($"Unauthorized report creation attempt for student with ID: {studentId}. Issuer ID: {issuerId}.")
+ {
+ StudentId = studentId;
+ IssuerId = issuerId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/UnauthorizedReportSearchAttemptException.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/UnauthorizedReportSearchAttemptException.cs
new file mode 100644
index 000000000..ecf336cd8
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Exceptions/UnauthorizedReportSearchAttemptException.cs
@@ -0,0 +1,16 @@
+namespace MiniSpace.Services.Reports.Application.Exceptions
+{
+ public class UnauthorizedReportSearchAttemptException : AppException
+ {
+ public override string Code { get; } = "unauthorized_report_search_attempt";
+ public Guid UserId { get; }
+ public string Role { get; }
+
+ public UnauthorizedReportSearchAttemptException(Guid userId, string role)
+ : base($"Unauthorized report search attempt by user with ID: '{userId}' and role: {role}.")
+ {
+ UserId = userId;
+ Role = role;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Extensions.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Extensions.cs
new file mode 100644
index 000000000..790a11250
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Extensions.cs
@@ -0,0 +1,16 @@
+using Convey;
+using Convey.CQRS.Commands;
+using Convey.CQRS.Events;
+
+namespace MiniSpace.Services.Reports.Application
+{
+ public static class Extensions
+ {
+ public static IConveyBuilder AddApplication(this IConveyBuilder builder)
+ => builder
+ .AddCommandHandlers()
+ .AddEventHandlers()
+ .AddInMemoryCommandDispatcher()
+ .AddInMemoryEventDispatcher();
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/IAppContext.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/IAppContext.cs
new file mode 100644
index 000000000..297000a52
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/IAppContext.cs
@@ -0,0 +1,8 @@
+namespace MiniSpace.Services.Reports.Application
+{
+ public interface IAppContext
+ {
+ string RequestId { get; }
+ IIdentityContext Identity { get; }
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/IIdentityContext.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/IIdentityContext.cs
new file mode 100644
index 000000000..49b00c27d
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/IIdentityContext.cs
@@ -0,0 +1,15 @@
+namespace MiniSpace.Services.Reports.Application
+{
+ public interface IIdentityContext
+ {
+ Guid Id { get; }
+ string Role { get; }
+ string Name { get; }
+ string Email { get; }
+ bool IsAuthenticated { get; }
+ bool IsAdmin { get; }
+ bool IsBanned { get; }
+ bool IsOrganizer { get; }
+ IDictionary Claims { get; }
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/MiniSpace.Services.Reports.Application.csproj b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/MiniSpace.Services.Reports.Application.csproj
new file mode 100644
index 000000000..f6e6a64af
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/MiniSpace.Services.Reports.Application.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net8.0
+ enable
+ disable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Queries/GetStudentReports.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Queries/GetStudentReports.cs
new file mode 100644
index 000000000..66ce81dab
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Queries/GetStudentReports.cs
@@ -0,0 +1,13 @@
+using Convey.CQRS.Queries;
+using MiniSpace.Services.Reports.Application.DTO;
+using MiniSpace.Services.Reports.Core.Wrappers;
+
+namespace MiniSpace.Services.Reports.Application.Queries
+{
+ public class GetStudentReports : IQuery>>
+ {
+ public Guid StudentId { get; set; }
+ public int Page { get; set; }
+ public int Results { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Services/IDateTimeProvider.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Services/IDateTimeProvider.cs
new file mode 100644
index 000000000..7fd1b751d
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Services/IDateTimeProvider.cs
@@ -0,0 +1,7 @@
+namespace MiniSpace.Services.Reports.Application.Services
+{
+ public interface IDateTimeProvider
+ {
+ DateTime Now { get; }
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Services/IEventMapper.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Services/IEventMapper.cs
new file mode 100644
index 000000000..3d420a78f
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Services/IEventMapper.cs
@@ -0,0 +1,11 @@
+using Convey.CQRS.Events;
+using MiniSpace.Services.Reports.Core.Events;
+
+namespace MiniSpace.Services.Reports.Application.Services
+{
+ public interface IEventMapper
+ {
+ IEvent Map(IDomainEvent @event);
+ IEnumerable MapAll(IEnumerable events);
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Services/IMessageBroker.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Services/IMessageBroker.cs
new file mode 100644
index 000000000..7db19df2f
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Services/IMessageBroker.cs
@@ -0,0 +1,10 @@
+using Convey.CQRS.Events;
+
+namespace MiniSpace.Services.Reports.Application.Services
+{
+ public interface IMessageBroker
+ {
+ Task PublishAsync(params IEvent[] events);
+ Task PublishAsync(IEnumerable events);
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Services/IReportValidator.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Services/IReportValidator.cs
new file mode 100644
index 000000000..73f193b00
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Services/IReportValidator.cs
@@ -0,0 +1,13 @@
+using MiniSpace.Services.Reports.Core.Entities;
+
+namespace MiniSpace.Services.Reports.Application.Services
+{
+ public interface IReportValidator
+ {
+ ContextType ParseContextType(string contextType);
+ ReportCategory ParseCategory(string category);
+ ReportState ParseStatus(string status);
+ void ValidateActiveReports(int activeReports);
+ void ValidateReason(string reason);
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Services/IReportsService.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Services/IReportsService.cs
new file mode 100644
index 000000000..bc97204a2
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Application/Services/IReportsService.cs
@@ -0,0 +1,11 @@
+using MiniSpace.Services.Reports.Application.Commands;
+using MiniSpace.Services.Reports.Application.DTO;
+using MiniSpace.Services.Reports.Core.Wrappers;
+
+namespace MiniSpace.Services.Reports.Application.Services
+{
+ public interface IReportsService
+ {
+ Task>> BrowseReportsAsync(SearchReports command);
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/AggregateId.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/AggregateId.cs
new file mode 100644
index 000000000..a91e04b3f
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/AggregateId.cs
@@ -0,0 +1,50 @@
+using MiniSpace.Services.Reports.Core.Exceptions;
+
+namespace MiniSpace.Services.Reports.Core.Entities
+{
+ public class AggregateId : IEquatable
+ {
+ public Guid Value { get; }
+
+ public AggregateId()
+ {
+ Value = Guid.NewGuid();
+ }
+
+ public AggregateId(Guid value)
+ {
+ if (value == Guid.Empty)
+ {
+ throw new InvalidAggregateIdException();
+ }
+
+ Value = value;
+ }
+
+ public bool Equals(AggregateId other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ return ReferenceEquals(this, other) || Value.Equals(other.Value);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ return obj.GetType() == GetType() && Equals((AggregateId) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return Value.GetHashCode();
+ }
+
+ public static implicit operator Guid(AggregateId id)
+ => id.Value;
+
+ public static implicit operator AggregateId(Guid id)
+ => new AggregateId(id);
+
+ public override string ToString() => Value.ToString();
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/AggregateRoot.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/AggregateRoot.cs
new file mode 100644
index 000000000..476bd1688
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/AggregateRoot.cs
@@ -0,0 +1,19 @@
+using MiniSpace.Services.Reports.Core.Events;
+
+namespace MiniSpace.Services.Reports.Core.Entities
+{
+ public abstract class AggregateRoot
+ {
+ private readonly List _events = new List();
+ public IEnumerable Events => _events;
+ public AggregateId Id { get; protected set; }
+ public int Version { get; protected set; }
+
+ protected void AddEvent(IDomainEvent @event)
+ {
+ _events.Add(@event);
+ }
+
+ public void ClearEvents() => _events.Clear();
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/Comment.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/Comment.cs
new file mode 100644
index 000000000..e0ca38cd9
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/Comment.cs
@@ -0,0 +1,12 @@
+namespace MiniSpace.Services.Reports.Core.Entities
+{
+ public class Comment
+ {
+ public Guid Id { get; private set; }
+
+ public Comment(Guid id)
+ {
+ Id = id;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/ContextType.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/ContextType.cs
new file mode 100644
index 000000000..56818d8c3
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/ContextType.cs
@@ -0,0 +1,10 @@
+namespace MiniSpace.Services.Reports.Core.Entities
+{
+ public enum ContextType
+ {
+ Event,
+ Post,
+ Comment,
+ StudentProfile
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/Event.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/Event.cs
new file mode 100644
index 000000000..3a404ef5c
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/Event.cs
@@ -0,0 +1,12 @@
+namespace MiniSpace.Services.Reports.Core.Entities
+{
+ public class Event
+ {
+ public Guid Id { get; private set; }
+
+ public Event(Guid id)
+ {
+ Id = id;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/Post.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/Post.cs
new file mode 100644
index 000000000..08ebcde28
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/Post.cs
@@ -0,0 +1,12 @@
+namespace MiniSpace.Services.Reports.Core.Entities
+{
+ public class Post
+ {
+ public Guid Id;
+
+ public Post(Guid id)
+ {
+ Id = id;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/Report.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/Report.cs
new file mode 100644
index 000000000..f74a9b65a
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/Report.cs
@@ -0,0 +1,81 @@
+using MiniSpace.Services.Reports.Core.Exceptions;
+
+namespace MiniSpace.Services.Reports.Core.Entities
+{
+ public class Report : AggregateRoot
+ {
+ public Guid IssuerId { get; private set; }
+ public Guid TargetId { get; private set; }
+ public Guid TargetOwnerId { get; private set; }
+ public ContextType ContextType { get; private set; }
+ public ReportCategory Category { get; private set; }
+ public string Reason { get; private set; }
+ public ReportState State { get; private set; }
+ public DateTime CreatedAt { get; private set; }
+ public DateTime UpdatedAt { get; private set; }
+ public Guid? ReviewerId { get; private set; }
+
+ public Report(Guid id, Guid issuerId, Guid targetId, Guid targetOwnerId, ContextType contextType,
+ ReportCategory category, string reason, ReportState state, DateTime createdAt, DateTime updatedAt,
+ Guid? reviewerId = null)
+ {
+ Id = id;
+ IssuerId = issuerId;
+ TargetId = targetId;
+ TargetOwnerId = targetOwnerId;
+ ContextType = contextType;
+ Category = category;
+ Reason = reason;
+ State = state;
+ CreatedAt = createdAt;
+ UpdatedAt = updatedAt;
+ ReviewerId = reviewerId;
+ }
+
+ public static Report Create(Guid id, Guid issuerId, Guid targetId, Guid targetOwnerId, ContextType contextType,
+ ReportCategory category, string reason, DateTime now)
+ => new Report(id, issuerId, targetId, targetOwnerId, contextType, category, reason, ReportState.Submitted,
+ now, now);
+
+ public void Cancel(DateTime now)
+ {
+ if (State != ReportState.Submitted)
+ {
+ throw new InvalidReportStateException(Id, ReportState.Submitted, State);
+ }
+ State = ReportState.Cancelled;
+ UpdatedAt = now;
+ }
+
+ public void StartReview(Guid reviewerId, DateTime now)
+ {
+ if (State != ReportState.Submitted)
+ {
+ throw new InvalidReportStateException(Id, ReportState.Submitted, State);
+ }
+ State = ReportState.UnderReview;
+ UpdatedAt = now;
+ ReviewerId = reviewerId;
+ }
+
+ public void Resolve(DateTime now)
+ {
+ if (State != ReportState.UnderReview)
+ {
+ throw new InvalidReportStateException(Id, ReportState.UnderReview, State);
+ }
+ State = ReportState.Resolved;
+ UpdatedAt = now;
+ }
+
+ public void Reject(DateTime now)
+ {
+ if (State != ReportState.UnderReview)
+ {
+ throw new InvalidReportStateException(Id, ReportState.UnderReview, State);
+ }
+ State = ReportState.Rejected;
+ UpdatedAt = now;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/ReportCategory.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/ReportCategory.cs
new file mode 100644
index 000000000..bca40922f
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/ReportCategory.cs
@@ -0,0 +1,14 @@
+namespace MiniSpace.Services.Reports.Core.Entities
+{
+ public enum ReportCategory
+ {
+ Spam,
+ HarassmentAndBullying,
+ Violence,
+ SexualContent,
+ Misinformation,
+ PrivacyViolations,
+ IntellectualPropertyViolations,
+ OtherViolations
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/ReportState.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/ReportState.cs
new file mode 100644
index 000000000..e3ec3ab5c
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/ReportState.cs
@@ -0,0 +1,11 @@
+namespace MiniSpace.Services.Reports.Core.Entities
+{
+ public enum ReportState
+ {
+ Submitted,
+ UnderReview,
+ Resolved,
+ Rejected,
+ Cancelled
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/Student.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/Student.cs
new file mode 100644
index 000000000..daa9eaad5
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Entities/Student.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace MiniSpace.Services.Reports.Core.Entities
+{
+ public class Student(Guid id)
+ {
+ public Guid Id { get; private set; } = id;
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Events/IDomainEvent.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Events/IDomainEvent.cs
new file mode 100644
index 000000000..4a7fc174d
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Events/IDomainEvent.cs
@@ -0,0 +1,7 @@
+namespace MiniSpace.Services.Reports.Core.Events
+{
+ public interface IDomainEvent
+ {
+
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Exceptions/DomainException.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Exceptions/DomainException.cs
new file mode 100644
index 000000000..32243d0bb
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Exceptions/DomainException.cs
@@ -0,0 +1,11 @@
+namespace MiniSpace.Services.Reports.Core.Exceptions
+{
+ public abstract class DomainException : Exception
+ {
+ public virtual string Code { get; }
+
+ protected DomainException(string message) : base(message)
+ {
+ }
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Exceptions/InvalidAggregateIdException.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Exceptions/InvalidAggregateIdException.cs
new file mode 100644
index 000000000..a5a786dab
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Exceptions/InvalidAggregateIdException.cs
@@ -0,0 +1,11 @@
+namespace MiniSpace.Services.Reports.Core.Exceptions
+{
+ public class InvalidAggregateIdException : DomainException
+ {
+ public override string Code { get; } = "invalid_aggregate_id";
+
+ public InvalidAggregateIdException() : base($"Invalid aggregate id.")
+ {
+ }
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Exceptions/InvalidReportStateException.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Exceptions/InvalidReportStateException.cs
new file mode 100644
index 000000000..688ea16a5
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Exceptions/InvalidReportStateException.cs
@@ -0,0 +1,20 @@
+using MiniSpace.Services.Reports.Core.Entities;
+
+namespace MiniSpace.Services.Reports.Core.Exceptions
+{
+ public class InvalidReportStateException : DomainException
+ {
+ public override string Code { get; } = "invalid_report_state";
+ public Guid ReportId { get; }
+ public ReportState RequiredState { get; }
+ public ReportState CurrentState { get; }
+
+ public InvalidReportStateException(Guid reportId, ReportState requiredState, ReportState currentState)
+ : base($"Report with id: {reportId} has invalid state: {currentState}. Required state: {requiredState}")
+ {
+ ReportId = reportId;
+ RequiredState = requiredState;
+ CurrentState = currentState;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/MiniSpace.Services.Reports.Core.csproj b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/MiniSpace.Services.Reports.Core.csproj
new file mode 100644
index 000000000..cf309aa85
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/MiniSpace.Services.Reports.Core.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net8.0
+ enable
+ disable
+
+
+
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Repositories/ICommentRepository.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Repositories/ICommentRepository.cs
new file mode 100644
index 000000000..d8b4dd7aa
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Repositories/ICommentRepository.cs
@@ -0,0 +1,12 @@
+using MiniSpace.Services.Reports.Core.Entities;
+
+namespace MiniSpace.Services.Reports.Core.Repositories
+{
+ public interface ICommentRepository
+ {
+ Task GetAsync(Guid id);
+ Task ExistsAsync(Guid id);
+ Task AddAsync(Comment comment);
+ Task DeleteAsync(Guid id);
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Repositories/IEventRepository.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Repositories/IEventRepository.cs
new file mode 100644
index 000000000..d254eeb28
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Repositories/IEventRepository.cs
@@ -0,0 +1,12 @@
+using MiniSpace.Services.Reports.Core.Entities;
+
+namespace MiniSpace.Services.Reports.Core.Repositories
+{
+ public interface IEventRepository
+ {
+ Task GetAsync(Guid id);
+ Task ExistsAsync(Guid id);
+ Task AddAsync(Event @event);
+ Task DeleteAsync(Guid id);
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Repositories/IPostRepository.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Repositories/IPostRepository.cs
new file mode 100644
index 000000000..4c6b62e58
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Repositories/IPostRepository.cs
@@ -0,0 +1,12 @@
+using MiniSpace.Services.Reports.Core.Entities;
+
+namespace MiniSpace.Services.Reports.Core.Repositories
+{
+ public interface IPostRepository
+ {
+ Task GetAsync(Guid id);
+ Task ExistsAsync(Guid id);
+ Task AddAsync(Post post);
+ Task DeleteAsync(Guid id);
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Repositories/IReportRepository.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Repositories/IReportRepository.cs
new file mode 100644
index 000000000..a9bdf907b
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Repositories/IReportRepository.cs
@@ -0,0 +1,18 @@
+using MiniSpace.Services.Reports.Core.Entities;
+
+namespace MiniSpace.Services.Reports.Core.Repositories
+{
+ public interface IReportRepository
+ {
+ Task GetAsync(Guid id);
+ Task> GetStudentActiveReportsAsync(Guid studentId);
+ Task AddAsync(Report report);
+ Task UpdateAsync(Report report);
+ Task DeleteAsync(Guid id);
+ Task<(IEnumerable reports, int pageNumber,int pageSize, int totalPages, int totalElements)> BrowseReportsAsync(
+ int pageNumber, int pageSize, IEnumerable contextTypes, IEnumerable states,
+ Guid reviewerId, IEnumerable sortBy, string direction);
+ Task<(IEnumerable reports, int pageNumber,int pageSize, int totalPages, int totalElements)> BrowseStudentReportsAsync(
+ int pageNumber, int pageSize, Guid studentId, IEnumerable sortBy, string direction);
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Repositories/IStudentRepository.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Repositories/IStudentRepository.cs
new file mode 100644
index 000000000..fb1a98fd0
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Repositories/IStudentRepository.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Threading.Tasks;
+using MiniSpace.Services.Reports.Core.Entities;
+
+namespace MiniSpace.Services.Reports.Core.Repositories
+{
+ public interface IStudentRepository
+ {
+ Task GetAsync(Guid id);
+ Task ExistsAsync(Guid id);
+ Task AddAsync(Student student);
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Wrappers/PagedResponse.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Wrappers/PagedResponse.cs
new file mode 100644
index 000000000..61d33a008
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Wrappers/PagedResponse.cs
@@ -0,0 +1,28 @@
+namespace MiniSpace.Services.Reports.Core.Wrappers
+{
+ public class PagedResponse : Response
+ {
+ public int TotalPages { get; }
+ public int TotalElements { get; }
+ public int Size { get; }
+ public int Number { get; }
+ public bool First { get; }
+ public bool Last { get; }
+ public bool Empty { get; }
+
+ public PagedResponse(T content, int pageNumber, int pageSize, int totalPages, int totalElements)
+ {
+ Content = content;
+ TotalPages = totalPages;
+ TotalElements = totalElements;
+ Size = pageSize;
+ Number = pageNumber;
+ First = pageNumber == 0;
+ Last = pageNumber == totalPages - 1;
+ Empty = totalElements == 0;
+ Succeeded = true;
+ Errors = null;
+ Message = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Wrappers/Response.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Wrappers/Response.cs
new file mode 100644
index 000000000..7955b1131
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Core/Wrappers/Response.cs
@@ -0,0 +1,10 @@
+namespace MiniSpace.Services.Reports.Core.Wrappers
+{
+ public class Response
+ {
+ public T Content { get; set; }
+ public bool Succeeded { get; set; }
+ public string[] Errors { get; set; }
+ public string Message { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Contexts/AppContext.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Contexts/AppContext.cs
new file mode 100644
index 000000000..a6d9620a8
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Contexts/AppContext.cs
@@ -0,0 +1,27 @@
+using MiniSpace.Services.Reports.Application;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Contexts
+{
+ internal class AppContext : IAppContext
+ {
+ public string RequestId { get; }
+ public IIdentityContext Identity { get; }
+
+ internal AppContext() : this(Guid.NewGuid().ToString("N"), IdentityContext.Empty)
+ {
+ }
+
+ internal AppContext(CorrelationContext context) : this(context.CorrelationId,
+ context.User is null ? IdentityContext.Empty : new IdentityContext(context.User))
+ {
+ }
+
+ internal AppContext(string requestId, IIdentityContext identity)
+ {
+ RequestId = requestId;
+ Identity = identity;
+ }
+
+ internal static IAppContext Empty => new AppContext();
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Contexts/AppContextFactory.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Contexts/AppContextFactory.cs
new file mode 100644
index 000000000..30907d2eb
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Contexts/AppContextFactory.cs
@@ -0,0 +1,35 @@
+using Convey.MessageBrokers;
+using Microsoft.AspNetCore.Http;
+using Newtonsoft.Json;
+using MiniSpace.Services.Reports.Application;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Contexts
+{
+ internal sealed class AppContextFactory : IAppContextFactory
+ {
+ private readonly ICorrelationContextAccessor _contextAccessor;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
+ public AppContextFactory(ICorrelationContextAccessor contextAccessor, IHttpContextAccessor httpContextAccessor)
+ {
+ _contextAccessor = contextAccessor;
+ _httpContextAccessor = httpContextAccessor;
+ }
+
+ public IAppContext Create()
+ {
+ if (_contextAccessor.CorrelationContext is { })
+ {
+ var payload = JsonConvert.SerializeObject(_contextAccessor.CorrelationContext);
+
+ return string.IsNullOrWhiteSpace(payload)
+ ? AppContext.Empty
+ : new AppContext(JsonConvert.DeserializeObject(payload));
+ }
+
+ var context = _httpContextAccessor.GetCorrelationContext();
+
+ return context is null ? AppContext.Empty : new AppContext(context);
+ }
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Contexts/CorrelationContext.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Contexts/CorrelationContext.cs
new file mode 100644
index 000000000..293495899
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Contexts/CorrelationContext.cs
@@ -0,0 +1,22 @@
+namespace MiniSpace.Services.Reports.Infrastructure.Contexts
+{
+ internal class CorrelationContext
+ {
+ public string CorrelationId { get; set; }
+ public string SpanContext { get; set; }
+ public UserContext User { get; set; }
+ public string ResourceId { get; set; }
+ public string TraceId { get; set; }
+ public string ConnectionId { get; set; }
+ public string Name { get; set; }
+ public DateTime CreatedAt { get; set; }
+
+ public class UserContext
+ {
+ public string Id { get; set; }
+ public bool IsAuthenticated { get; set; }
+ public string Role { get; set; }
+ public IDictionary Claims { get; set; }
+ }
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Contexts/IdentityContext.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Contexts/IdentityContext.cs
new file mode 100644
index 000000000..f8e7a308e
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Contexts/IdentityContext.cs
@@ -0,0 +1,41 @@
+using MiniSpace.Services.Reports.Application;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Contexts
+{
+ internal class IdentityContext : IIdentityContext
+ {
+ public Guid Id { get; }
+ public string Role { get; } = string.Empty;
+ public string Name { get; } = string.Empty;
+ public string Email { get; } = string.Empty;
+ public bool IsAuthenticated { get; }
+ public bool IsAdmin { get; }
+ public bool IsBanned { get; }
+ public bool IsOrganizer { get; }
+ public IDictionary Claims { get; } = new Dictionary();
+
+ internal IdentityContext()
+ {
+ }
+
+ internal IdentityContext(CorrelationContext.UserContext context)
+ : this(context.Id, context.Role, context.IsAuthenticated, context.Claims)
+ {
+ }
+
+ internal IdentityContext(string id, string role, bool isAuthenticated, IDictionary claims)
+ {
+ Id = Guid.TryParse(id, out var userId) ? userId : Guid.Empty;
+ Role = role ?? string.Empty;
+ IsAuthenticated = isAuthenticated;
+ IsAdmin = Role.Equals("admin", StringComparison.InvariantCultureIgnoreCase);
+ IsBanned = Role.Equals("banned", StringComparison.InvariantCultureIgnoreCase);
+ IsOrganizer = Role.Equals("organizer", StringComparison.InvariantCultureIgnoreCase);
+ Claims = claims ?? new Dictionary();
+ Name = Claims.TryGetValue("name", out var name) ? name : string.Empty;
+ Email = Claims.TryGetValue("email", out var email) ? email : string.Empty;
+ }
+
+ internal static IIdentityContext Empty => new IdentityContext();
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Decorators/OutboxCommandHandlerDecorator.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Decorators/OutboxCommandHandlerDecorator.cs
new file mode 100644
index 000000000..3400f96a2
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Decorators/OutboxCommandHandlerDecorator.cs
@@ -0,0 +1,35 @@
+using Convey.CQRS.Commands;
+using Convey.MessageBrokers;
+using Convey.MessageBrokers.Outbox;
+using Convey.Types;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Decorators
+{
+ [Decorator]
+ internal sealed class OutboxCommandHandlerDecorator : ICommandHandler
+ where TCommand : class, ICommand
+ {
+ private readonly ICommandHandler _handler;
+ private readonly IMessageOutbox _outbox;
+ private readonly string _messageId;
+ private readonly bool _enabled;
+
+ public OutboxCommandHandlerDecorator(ICommandHandler handler, IMessageOutbox outbox,
+ OutboxOptions outboxOptions, IMessagePropertiesAccessor messagePropertiesAccessor)
+ {
+ _handler = handler;
+ _outbox = outbox;
+ _enabled = outboxOptions.Enabled;
+
+ var messageProperties = messagePropertiesAccessor.MessageProperties;
+ _messageId = string.IsNullOrWhiteSpace(messageProperties?.MessageId)
+ ? Guid.NewGuid().ToString("N")
+ : messageProperties.MessageId;
+ }
+
+ public Task HandleAsync(TCommand command, CancellationToken cancellationToken)
+ => _enabled
+ ? _outbox.HandleAsync(_messageId, () => _handler.HandleAsync(command))
+ : _handler.HandleAsync(command);
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Decorators/OutboxEventHandlerDecorator.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Decorators/OutboxEventHandlerDecorator.cs
new file mode 100644
index 000000000..6687e433e
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Decorators/OutboxEventHandlerDecorator.cs
@@ -0,0 +1,35 @@
+using Convey.CQRS.Events;
+using Convey.MessageBrokers;
+using Convey.MessageBrokers.Outbox;
+using Convey.Types;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Decorators
+{
+ [Decorator]
+ internal sealed class OutboxEventHandlerDecorator : IEventHandler
+ where TEvent : class, IEvent
+ {
+ private readonly IEventHandler _handler;
+ private readonly IMessageOutbox _outbox;
+ private readonly string _messageId;
+ private readonly bool _enabled;
+
+ public OutboxEventHandlerDecorator(IEventHandler handler, IMessageOutbox outbox,
+ OutboxOptions outboxOptions, IMessagePropertiesAccessor messagePropertiesAccessor)
+ {
+ _handler = handler;
+ _outbox = outbox;
+ _enabled = outboxOptions.Enabled;
+
+ var messageProperties = messagePropertiesAccessor.MessageProperties;
+ _messageId = string.IsNullOrWhiteSpace(messageProperties?.MessageId)
+ ? Guid.NewGuid().ToString("N")
+ : messageProperties.MessageId;
+ }
+
+ public Task HandleAsync(TEvent @event, CancellationToken cancellationToken)
+ => _enabled
+ ? _outbox.HandleAsync(_messageId, () => _handler.HandleAsync(@event))
+ : _handler.HandleAsync(@event);
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Exceptions/ExceptionToMessageMapper.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Exceptions/ExceptionToMessageMapper.cs
new file mode 100644
index 000000000..7c568efe0
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Exceptions/ExceptionToMessageMapper.cs
@@ -0,0 +1,19 @@
+using Convey.MessageBrokers.RabbitMQ;
+// using MiniSpace.Services.Reports.Application.Commands;
+// using MiniSpace.Services.Reports.Application.Events.Rejected;
+// using MiniSpace.Services.Reports.Application.Events.External;
+// using MiniSpace.Services.Reports.Application.Exceptions;
+// using MiniSpace.Services.Reports.Core;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Exceptions
+{
+ internal sealed class ExceptionToMessageMapper : IExceptionToMessageMapper
+ {
+ public object Map(Exception exception, object message)
+ => exception switch
+
+ {
+ _ => null
+ };
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Exceptions/ExceptionToResponseMapper.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Exceptions/ExceptionToResponseMapper.cs
new file mode 100644
index 000000000..d40597dfb
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Exceptions/ExceptionToResponseMapper.cs
@@ -0,0 +1,46 @@
+using System.Collections.Concurrent;
+using System.Net;
+using Convey;
+using Convey.WebApi.Exceptions;
+using MiniSpace.Services.Reports.Application.Exceptions;
+using MiniSpace.Services.Reports.Core.Exceptions;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Exceptions
+{
+ internal sealed class ExceptionToResponseMapper : IExceptionToResponseMapper
+ {
+ private static readonly ConcurrentDictionary Codes = new ConcurrentDictionary();
+
+ public ExceptionResponse Map(Exception exception)
+ => exception switch
+ {
+ DomainException ex => new ExceptionResponse(new {code = GetCode(ex), reason = ex.Message},
+ HttpStatusCode.BadRequest),
+ AppException ex => new ExceptionResponse(new {code = GetCode(ex), reason = ex.Message},
+ HttpStatusCode.BadRequest),
+ _ => new ExceptionResponse(new {code = "error", reason = "There was an error."},
+ HttpStatusCode.BadRequest)
+ };
+
+ private static string GetCode(Exception exception)
+ {
+ var type = exception.GetType();
+ if (Codes.TryGetValue(type, out var code))
+ {
+ return code;
+ }
+
+ var exceptionCode = exception switch
+ {
+ DomainException domainException when !string.IsNullOrWhiteSpace(domainException.Code) => domainException
+ .Code,
+ AppException appException when !string.IsNullOrWhiteSpace(appException.Code) => appException.Code,
+ _ => exception.GetType().Name.Underscore().Replace("_exception", string.Empty)
+ };
+
+ Codes.TryAdd(type, exceptionCode);
+
+ return exceptionCode;
+ }
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Extensions.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Extensions.cs
new file mode 100644
index 000000000..f07933ed6
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Extensions.cs
@@ -0,0 +1,142 @@
+using System.Text;
+using Convey;
+using Convey.CQRS.Commands;
+using Convey.CQRS.Events;
+using Convey.CQRS.Queries;
+using Convey.Discovery.Consul;
+using Convey.Docs.Swagger;
+using Convey.HTTP;
+using Convey.LoadBalancing.Fabio;
+using Convey.MessageBrokers;
+using Convey.MessageBrokers.CQRS;
+using Convey.MessageBrokers.Outbox;
+using Convey.MessageBrokers.Outbox.Mongo;
+using Convey.MessageBrokers.RabbitMQ;
+using Convey.Metrics.AppMetrics;
+using Convey.Persistence.MongoDB;
+using Convey.Persistence.Redis;
+using Convey.Security;
+using Convey.Tracing.Jaeger;
+using Convey.Tracing.Jaeger.RabbitMQ;
+using Convey.WebApi;
+using Convey.WebApi.CQRS;
+using Convey.WebApi.Security;
+using Convey.WebApi.Swagger;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Newtonsoft.Json;
+using MiniSpace.Services.Reports.Application;
+using MiniSpace.Services.Reports.Application.Commands;
+using MiniSpace.Services.Reports.Application.Events.External;
+using MiniSpace.Services.Reports.Application.Events.External.Handlers;
+using MiniSpace.Services.Reports.Application.Services;
+using MiniSpace.Services.Reports.Core.Repositories;
+using MiniSpace.Services.Reports.Infrastructure.Contexts;
+using MiniSpace.Services.Reports.Infrastructure.Decorators;
+using MiniSpace.Services.Reports.Infrastructure.Exceptions;
+using MiniSpace.Services.Reports.Infrastructure.Logging;
+using MiniSpace.Services.Reports.Infrastructure.Mongo.Documents;
+using MiniSpace.Services.Reports.Infrastructure.Mongo.Repositories;
+using MiniSpace.Services.Reports.Infrastructure.Services;
+
+namespace MiniSpace.Services.Reports.Infrastructure
+{
+ public static class Extensions
+ {
+ public static IConveyBuilder AddInfrastructure(this IConveyBuilder builder)
+ {
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+ builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+ builder.Services.AddTransient(ctx => ctx.GetRequiredService().Create());
+ builder.Services.TryDecorate(typeof(ICommandHandler<>), typeof(OutboxCommandHandlerDecorator<>));
+ builder.Services.TryDecorate(typeof(IEventHandler<>), typeof(OutboxEventHandlerDecorator<>));
+
+ return builder
+ .AddErrorHandler()
+ .AddQueryHandlers()
+ .AddInMemoryQueryDispatcher()
+ .AddHttpClient()
+ .AddConsul()
+ .AddFabio()
+ .AddRabbitMq(plugins: p => p.AddJaegerRabbitMqPlugin())
+ .AddMessageOutbox(o => o.AddMongo())
+ .AddExceptionToMessageMapper()
+ .AddMongo()
+ .AddRedis()
+ .AddMetrics()
+ .AddJaeger()
+ .AddHandlersLogging()
+ .AddMongoRepository("reports")
+ .AddMongoRepository("events")
+ .AddMongoRepository("posts")
+ .AddMongoRepository("comments")
+ .AddMongoRepository("students")
+ .AddWebApiSwaggerDocs()
+ .AddCertificateAuthentication()
+ .AddSecurity();
+ }
+
+ public static IApplicationBuilder UseInfrastructure(this IApplicationBuilder app)
+ {
+ app.UseErrorHandler()
+ .UseSwaggerDocs()
+ .UseJaeger()
+ .UseConvey()
+ .UsePublicContracts()
+ .UseMetrics()
+ .UseCertificateAuthentication()
+ .UseRabbitMq()
+ .SubscribeEvent()
+ .SubscribeEvent()
+ .SubscribeEvent();
+
+ return app;
+ }
+
+ internal static CorrelationContext GetCorrelationContext(this IHttpContextAccessor accessor)
+ => accessor.HttpContext?.Request.Headers.TryGetValue("Correlation-Context", out var json) is true
+ ? JsonConvert.DeserializeObject(json.FirstOrDefault())
+ : null;
+
+ internal static IDictionary GetHeadersToForward(this IMessageProperties messageProperties)
+ {
+ const string sagaHeader = "Saga";
+ if (messageProperties?.Headers is null || !messageProperties.Headers.TryGetValue(sagaHeader, out var saga))
+ {
+ return null;
+ }
+
+ return saga is null
+ ? null
+ : new Dictionary
+ {
+ [sagaHeader] = saga
+ };
+ }
+
+ internal static string GetSpanContext(this IMessageProperties messageProperties, string header)
+ {
+ if (messageProperties is null)
+ {
+ return string.Empty;
+ }
+
+ if (messageProperties.Headers.TryGetValue(header, out var span) && span is byte[] spanBytes)
+ {
+ return Encoding.UTF8.GetString(spanBytes);
+ }
+
+ return string.Empty;
+ }
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/IAppContextFactory.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/IAppContextFactory.cs
new file mode 100644
index 000000000..4fc73f0ee
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/IAppContextFactory.cs
@@ -0,0 +1,9 @@
+using MiniSpace.Services.Reports.Application;
+
+namespace MiniSpace.Services.Reports.Infrastructure
+{
+ public interface IAppContextFactory
+ {
+ IAppContext Create();
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Logging/Extensions.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Logging/Extensions.cs
new file mode 100644
index 000000000..7efe1fbab
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Logging/Extensions.cs
@@ -0,0 +1,21 @@
+using Convey;
+using Convey.Logging.CQRS;
+using Microsoft.Extensions.DependencyInjection;
+using MiniSpace.Services.Reports.Application.Commands;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Logging
+{
+ internal static class Extensions
+ {
+ public static IConveyBuilder AddHandlersLogging(this IConveyBuilder builder)
+ {
+ var assembly = typeof(CreateReport).Assembly;
+
+ builder.Services.AddSingleton(new MessageToLogTemplateMapper());
+
+ return builder
+ .AddCommandHandlersLogging(assembly)
+ .AddEventHandlersLogging(assembly);
+ }
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Logging/MessageToLogTemplateMapper.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Logging/MessageToLogTemplateMapper.cs
new file mode 100644
index 000000000..b72c79ceb
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Logging/MessageToLogTemplateMapper.cs
@@ -0,0 +1,56 @@
+using Convey.Logging.CQRS;
+using MiniSpace.Services.Reports.Application.Commands;
+using MiniSpace.Services.Reports.Application.Events.External;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Logging
+{
+ internal sealed class MessageToLogTemplateMapper : IMessageToLogTemplateMapper
+ {
+ private static IReadOnlyDictionary MessageTemplates
+ => new Dictionary
+ {
+ {
+ typeof(CreateReport), new HandlerLogTemplate
+ {
+ After = "Created a new report with ID: {ReportId}."
+ }
+ },
+ {
+ typeof(CancelReport), new HandlerLogTemplate
+ {
+ After = "Canceled a report with ID: {ReportId}."
+ }
+ },
+ {
+ typeof(DeleteReport), new HandlerLogTemplate
+ {
+ After = "Deleted a report with ID: {ReportId}."
+ }
+ },
+ {
+ typeof(StartReportReview), new HandlerLogTemplate
+ {
+ After = "Started a review for a report with ID: {ReportId}."
+ }
+ },
+ {
+ typeof(ResolveReport), new HandlerLogTemplate
+ {
+ After = "Resolved a report with ID: {ReportId}."
+ }
+ },
+ {
+ typeof(RejectReport), new HandlerLogTemplate
+ {
+ After = "Rejected a report with ID: {ReportId}."
+ }
+ },
+ };
+
+ public HandlerLogTemplate Map(TMessage message) where TMessage : class
+ {
+ var key = message.GetType();
+ return MessageTemplates.TryGetValue(key, out var template) ? template : null;
+ }
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/MiniSpace.Services.Reports.Infrastructure.csproj b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/MiniSpace.Services.Reports.Infrastructure.csproj
new file mode 100644
index 000000000..e116eb0bb
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/MiniSpace.Services.Reports.Infrastructure.csproj
@@ -0,0 +1,39 @@
+
+
+
+ net8.0
+ enable
+ disable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Documents/CommentDocument.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Documents/CommentDocument.cs
new file mode 100644
index 000000000..b27bbd5db
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Documents/CommentDocument.cs
@@ -0,0 +1,9 @@
+using Convey.Types;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Mongo.Documents
+{
+ public class CommentDocument: IIdentifiable
+ {
+ public Guid Id { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Documents/EventDocument.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Documents/EventDocument.cs
new file mode 100644
index 000000000..5f8363c13
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Documents/EventDocument.cs
@@ -0,0 +1,9 @@
+using Convey.Types;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Mongo.Documents
+{
+ public class EventDocument: IIdentifiable
+ {
+ public Guid Id { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Documents/Extensions.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Documents/Extensions.cs
new file mode 100644
index 000000000..70953c80d
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Documents/Extensions.cs
@@ -0,0 +1,81 @@
+using MiniSpace.Services.Reports.Application.DTO;
+using MiniSpace.Services.Reports.Core.Entities;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Mongo.Documents
+{
+ public static class Extensions
+ {
+ public static Report AsEntity(this ReportDocument document)
+ => new Report(document.Id, document.IssuerId, document.TargetId, document.TargetOwnerId,
+ document.ContextType, document.Category, document.Reason, document.State, document.CreatedAt,
+ document.UpdatedAt, document.ReviewerId);
+
+ public static ReportDocument AsDocument(this Report entity)
+ => new ReportDocument()
+ {
+ Id = entity.Id,
+ IssuerId = entity.IssuerId,
+ TargetId = entity.TargetId,
+ TargetOwnerId = entity.TargetOwnerId,
+ ContextType = entity.ContextType,
+ Category = entity.Category,
+ Reason = entity.Reason,
+ State = entity.State,
+ CreatedAt = entity.CreatedAt,
+ UpdatedAt = entity.UpdatedAt,
+ ReviewerId = entity.ReviewerId
+ };
+
+ public static ReportDto AsDto(this ReportDocument document)
+ => new ReportDto()
+ {
+ Id = document.Id,
+ IssuerId = document.IssuerId,
+ TargetId = document.TargetId,
+ TargetOwnerId = document.TargetOwnerId,
+ ContextType = document.ContextType.ToString(),
+ Category = document.Category.ToString(),
+ Reason = document.Reason,
+ State = document.State.ToString(),
+ CreatedAt = document.CreatedAt,
+ UpdatedAt = document.UpdatedAt,
+ ReviewerId = document.ReviewerId
+ };
+
+ public static Event AsEntity(this EventDocument document)
+ => new Event(document.Id);
+
+ public static EventDocument AsDocument(this Event entity)
+ => new EventDocument
+ {
+ Id = entity.Id,
+ };
+
+ public static Post AsEntity(this PostDocument document)
+ => new Post(document.Id);
+
+ public static PostDocument AsDocument(this Post entity)
+ => new PostDocument()
+ {
+ Id = entity.Id,
+ };
+
+ public static Comment AsEntity(this CommentDocument document)
+ => new Comment(document.Id);
+
+ public static CommentDocument AsDocument(this Comment entity)
+ => new CommentDocument()
+ {
+ Id = entity.Id,
+ };
+
+ public static StudentDocument AsDocument(this Student entity)
+ => new ()
+ {
+ Id = entity.Id,
+ };
+
+ public static Student AsEntity(this StudentDocument document)
+ => new (document.Id);
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Documents/PostDocument.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Documents/PostDocument.cs
new file mode 100644
index 000000000..1db0c2a6c
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Documents/PostDocument.cs
@@ -0,0 +1,9 @@
+using Convey.Types;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Mongo.Documents
+{
+ public class PostDocument: IIdentifiable
+ {
+ public Guid Id { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Documents/ReportDocument.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Documents/ReportDocument.cs
new file mode 100644
index 000000000..b198c1d59
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Documents/ReportDocument.cs
@@ -0,0 +1,20 @@
+using Convey.Types;
+using MiniSpace.Services.Reports.Core.Entities;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Mongo.Documents
+{
+ public class ReportDocument: IIdentifiable
+ {
+ public Guid Id { get; set; }
+ public Guid IssuerId { get; set; }
+ public Guid TargetId { get; set; }
+ public Guid TargetOwnerId { get; set; }
+ public ContextType ContextType { get; set; }
+ public ReportCategory Category { get; set; }
+ public string Reason { get; set; }
+ public ReportState State { get; set; }
+ public DateTime CreatedAt { get; set; }
+ public DateTime UpdatedAt { get; set; }
+ public Guid? ReviewerId { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Documents/StudentDocument.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Documents/StudentDocument.cs
new file mode 100644
index 000000000..e910924f8
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Documents/StudentDocument.cs
@@ -0,0 +1,10 @@
+using System;
+using Convey.Types;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Mongo.Documents
+{
+ public class StudentDocument: IIdentifiable
+ {
+ public Guid Id { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Queries/Handlers/GetStudentReportsHandler.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Queries/Handlers/GetStudentReportsHandler.cs
new file mode 100644
index 000000000..5af0f0339
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Queries/Handlers/GetStudentReportsHandler.cs
@@ -0,0 +1,37 @@
+using Convey.CQRS.Queries;
+using MiniSpace.Services.Reports.Application;
+using MiniSpace.Services.Reports.Application.DTO;
+using MiniSpace.Services.Reports.Application.Queries;
+using MiniSpace.Services.Reports.Core.Repositories;
+using MiniSpace.Services.Reports.Core.Wrappers;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Mongo.Queries.Handlers
+{
+ public class GetStudentReportsHandler : IQueryHandler>>
+ {
+ private readonly IReportRepository _reportRepository;
+ private readonly IAppContext _appContext;
+
+ public GetStudentReportsHandler(IReportRepository reportRepository, IAppContext appContext)
+ {
+ _reportRepository = reportRepository;
+ _appContext = appContext;
+ }
+
+ public async Task>> HandleAsync(GetStudentReports query, CancellationToken cancellationToken)
+ {
+ var identity = _appContext.Identity;
+ if (identity.IsAuthenticated && identity.Id != query.StudentId)
+ {
+ return new PagedResponse>(Enumerable.Empty(),
+ 1, query.Results, 0, 0);
+ }
+
+ var result = await _reportRepository.BrowseStudentReportsAsync(query.Page, query.Results,
+ query.StudentId, Enumerable.Empty(), "dsc");
+
+ return new PagedResponse>(result.reports.Select(r => new ReportDto(r)),
+ result.pageNumber, result.pageSize, result.totalPages, result.totalElements);
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Repositories/CommentMongoRepository.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Repositories/CommentMongoRepository.cs
new file mode 100644
index 000000000..cc4f1b775
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Repositories/CommentMongoRepository.cs
@@ -0,0 +1,33 @@
+using Convey.Persistence.MongoDB;
+using MiniSpace.Services.Reports.Core.Entities;
+using MiniSpace.Services.Reports.Core.Repositories;
+using MiniSpace.Services.Reports.Infrastructure.Mongo.Documents;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Mongo.Repositories
+{
+ public class CommentMongoRepository : ICommentRepository
+ {
+ private readonly IMongoRepository _repository;
+
+ public CommentMongoRepository(IMongoRepository repository)
+ {
+ _repository = repository;
+ }
+
+ public async Task GetAsync(Guid id)
+ {
+ var comment = await _repository.GetAsync(c => c.Id == id);
+
+ return comment?.AsEntity();
+ }
+
+ public Task ExistsAsync(Guid id)
+ => _repository.ExistsAsync(c => c.Id == id);
+
+ public Task AddAsync(Comment comment)
+ => _repository.AddAsync(comment.AsDocument());
+
+ public Task DeleteAsync(Guid id)
+ => _repository.DeleteAsync(id);
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Repositories/EventMongoRepository.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Repositories/EventMongoRepository.cs
new file mode 100644
index 000000000..05dd8abab
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Repositories/EventMongoRepository.cs
@@ -0,0 +1,33 @@
+using Convey.Persistence.MongoDB;
+using MiniSpace.Services.Reports.Core.Entities;
+using MiniSpace.Services.Reports.Core.Repositories;
+using MiniSpace.Services.Reports.Infrastructure.Mongo.Documents;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Mongo.Repositories
+{
+ public class EventMongoRepository : IEventRepository
+ {
+ private readonly IMongoRepository _repository;
+
+ public EventMongoRepository(IMongoRepository repository)
+ {
+ _repository = repository;
+ }
+
+ public async Task GetAsync(Guid id)
+ {
+ var @event = await _repository.GetAsync(e => e.Id == id);
+
+ return @event?.AsEntity();
+ }
+
+ public Task ExistsAsync(Guid id)
+ => _repository.ExistsAsync(e => e.Id == id);
+
+ public Task AddAsync(Event @event)
+ => _repository.AddAsync(@event.AsDocument());
+
+ public Task DeleteAsync(Guid id)
+ => _repository.DeleteAsync(id);
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Repositories/Extensions.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Repositories/Extensions.cs
new file mode 100644
index 000000000..12dfc1689
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Repositories/Extensions.cs
@@ -0,0 +1,117 @@
+using System.Collections;
+using MiniSpace.Services.Reports.Core.Entities;
+using MiniSpace.Services.Reports.Infrastructure.Mongo.Documents;
+using MongoDB.Driver;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Mongo.Repositories
+{
+ public static class Extensions
+ {
+ private static readonly FilterDefinitionBuilder FilterDefinitionBuilder = Builders.Filter;
+ public static async Task<(int totalPages, int totalElements, IReadOnlyList data)> AggregateByPage(
+ this IMongoCollection collection,
+ FilterDefinition filterDefinition,
+ SortDefinition sortDefinition,
+ int page,
+ int pageSize)
+ {
+ var countFacet = AggregateFacet.Create("count",
+ PipelineDefinition.Create(new[]
+ {
+ PipelineStageDefinitionBuilder.Count()
+ }));
+
+ var dataFacet = AggregateFacet.Create("data",
+ PipelineDefinition.Create(new[]
+ {
+ PipelineStageDefinitionBuilder.Sort(sortDefinition),
+ PipelineStageDefinitionBuilder.Skip((page - 1) * pageSize),
+ PipelineStageDefinitionBuilder.Limit(pageSize),
+ }));
+
+
+ var aggregation = await collection.Aggregate()
+ .Match(filterDefinition)
+ .Facet(countFacet, dataFacet)
+ .ToListAsync();
+
+ var count = aggregation.First()
+ .Facets.First(x => x.Name == "count")
+ .Output()
+ ?.FirstOrDefault()
+ ?.Count;
+
+ if (count == null)
+ {
+ return (0, 0, Array.Empty());
+ }
+ var totalPages = (int)Math.Ceiling((double)count / pageSize);
+
+ var data = aggregation.First()
+ .Facets.First(x => x.Name == "data")
+ .Output();
+
+ return (totalPages, (int)count, data);
+ }
+
+ public static FilterDefinition ToFilterDefinition()
+ {
+ var filterDefinition = FilterDefinitionBuilder.Empty;
+
+ return filterDefinition;
+ }
+
+ public static FilterDefinition AddContextTypesFilter (this FilterDefinition filterDefinition,
+ IEnumerable contextTypesEnumerable)
+ {
+ var contextTypes = contextTypesEnumerable.ToList();
+ if(contextTypes.Any())
+ {
+ filterDefinition &= FilterDefinitionBuilder.In(x => x.ContextType, contextTypes);
+ }
+ return filterDefinition;
+ }
+
+ public static FilterDefinition AddStatesFilter (this FilterDefinition filterDefinition,
+ IEnumerable statesEnumerable)
+ {
+ var states = statesEnumerable.ToList();
+ if(states.Count != 0)
+ {
+ filterDefinition &= FilterDefinitionBuilder.In(x => x.State, states);
+ }
+ return filterDefinition;
+ }
+
+ public static FilterDefinition AddReviewerIdFilter(this FilterDefinition filterDefinition, Guid reviewerId)
+ {
+ if (reviewerId != Guid.Empty)
+ {
+ filterDefinition &= FilterDefinitionBuilder.Eq(x => x.ReviewerId, reviewerId);
+ }
+ return filterDefinition;
+ }
+
+ public static FilterDefinition AddStudentIdFilter(this FilterDefinition filterDefinition, Guid studentId)
+ {
+ filterDefinition &= FilterDefinitionBuilder.Eq(x => x.IssuerId, studentId);
+ return filterDefinition;
+ }
+
+ public static SortDefinition ToSortDefinition(IEnumerable sortByArguments, string direction)
+ {
+ var sort = sortByArguments.ToList();
+ if(sort.Count == 0)
+ {
+ sort.Add("UpdatedAt");
+ }
+ var sortDefinitionBuilder = Builders.Sort;
+ var sortDefinition = sort
+ .Select(sortBy => direction == "asc"
+ ? sortDefinitionBuilder.Ascending(sortBy)
+ : sortDefinitionBuilder.Descending(sortBy));
+ var sortCombined = sortDefinitionBuilder.Combine(sortDefinition);
+ return sortCombined;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Repositories/PostMongoRepository.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Repositories/PostMongoRepository.cs
new file mode 100644
index 000000000..d2658e569
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Repositories/PostMongoRepository.cs
@@ -0,0 +1,33 @@
+using Convey.Persistence.MongoDB;
+using MiniSpace.Services.Reports.Core.Entities;
+using MiniSpace.Services.Reports.Core.Repositories;
+using MiniSpace.Services.Reports.Infrastructure.Mongo.Documents;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Mongo.Repositories
+{
+ public class PostMongoRepository : IPostRepository
+ {
+ private readonly IMongoRepository _repository;
+
+ public PostMongoRepository(IMongoRepository repository)
+ {
+ _repository = repository;
+ }
+
+ public async Task GetAsync(Guid id)
+ {
+ var post = await _repository.GetAsync(p => p.Id == id);
+
+ return post?.AsEntity();
+ }
+
+ public Task ExistsAsync(Guid id)
+ => _repository.ExistsAsync(p => p.Id == id);
+
+ public Task AddAsync(Post post)
+ => _repository.AddAsync(post.AsDocument());
+
+ public Task DeleteAsync(Guid id)
+ => _repository.DeleteAsync(id);
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Repositories/ReportMongoRepository.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Repositories/ReportMongoRepository.cs
new file mode 100644
index 000000000..e112ecd86
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Repositories/ReportMongoRepository.cs
@@ -0,0 +1,84 @@
+using Convey.Persistence.MongoDB;
+using MiniSpace.Services.Reports.Core.Entities;
+using MiniSpace.Services.Reports.Core.Repositories;
+using MiniSpace.Services.Reports.Infrastructure.Mongo.Documents;
+using MongoDB.Driver;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Mongo.Repositories
+{
+ public class ReportMongoRepository : IReportRepository
+ {
+ private readonly IMongoRepository _repository;
+
+ public ReportMongoRepository(IMongoRepository repository)
+ {
+ _repository = repository;
+ }
+
+ public async Task GetAsync(Guid id)
+ {
+ var report = await _repository.GetAsync(r => r.Id == id);
+
+ return report?.AsEntity();
+ }
+
+ public async Task> GetStudentActiveReportsAsync(Guid studentId)
+ {
+ var reports = await _repository.FindAsync(r => r.IssuerId == studentId
+ && r.State == ReportState.Submitted || r.State == ReportState.UnderReview);
+
+ return reports.Select(r => r.AsEntity());
+ }
+
+ public Task AddAsync(Report report)
+ => _repository.AddAsync(report.AsDocument());
+
+ public Task UpdateAsync(Report report)
+ => _repository.UpdateAsync(report.AsDocument());
+
+ public Task DeleteAsync(Guid id)
+ => _repository.DeleteAsync(id);
+
+ private async Task<(int totalPages, int totalElements, IEnumerable data)> BrowseAsync(
+ FilterDefinition filterDefinition, SortDefinition sortDefinition,
+ int pageNumber, int pageSize)
+ {
+ var pagedEvents = await _repository.Collection.AggregateByPage(
+ filterDefinition,
+ sortDefinition,
+ pageNumber,
+ pageSize);
+
+ return pagedEvents;
+ }
+
+ public async Task<(IEnumerable reports, int pageNumber,int pageSize, int totalPages, int totalElements)> BrowseReportsAsync(int pageNumber, int pageSize,
+ IEnumerable contextTypes, IEnumerable states, Guid reviewerId,
+ IEnumerable sortBy, string direction)
+ {
+ var filterDefinition = Extensions.ToFilterDefinition()
+ .AddContextTypesFilter(contextTypes)
+ .AddStatesFilter(states)
+ .AddReviewerIdFilter(reviewerId);
+ var sortDefinition = Extensions.ToSortDefinition(sortBy, direction);
+
+ var pagedEvents = await BrowseAsync(filterDefinition, sortDefinition, pageNumber, pageSize);
+
+ return (pagedEvents.data.Select(r => r.AsEntity()), pageNumber, pageSize,
+ pagedEvents.totalPages, pagedEvents.totalElements);
+ }
+
+ public async Task<(IEnumerable reports, int pageNumber, int pageSize, int totalPages, int totalElements)> BrowseStudentReportsAsync(int pageNumber, int pageSize,
+ Guid studentId, IEnumerable sortBy, string direction)
+ {
+ var filterDefinition = Extensions.ToFilterDefinition()
+ .AddStudentIdFilter(studentId);
+ var sortDefinition = Extensions.ToSortDefinition(sortBy, direction);
+
+ var pagedEvents = await BrowseAsync(filterDefinition, sortDefinition, pageNumber, pageSize);
+
+ return (pagedEvents.data.Select(r => r.AsEntity()), pageNumber, pageSize,
+ pagedEvents.totalPages, pagedEvents.totalElements);
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Repositories/StudentMongoRepository.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Repositories/StudentMongoRepository.cs
new file mode 100644
index 000000000..2fade5d17
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Mongo/Repositories/StudentMongoRepository.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Threading.Tasks;
+using Convey.Persistence.MongoDB;
+using MiniSpace.Services.Reports.Core.Entities;
+using MiniSpace.Services.Reports.Core.Repositories;
+using MiniSpace.Services.Reports.Infrastructure.Mongo.Documents;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Mongo.Repositories
+{
+ public class StudentMongoRepository : IStudentRepository
+ {
+ private readonly IMongoRepository _repository;
+
+ public StudentMongoRepository(IMongoRepository repository)
+ {
+ _repository = repository;
+ }
+ public async Task GetAsync(Guid id)
+ {
+ var student = await _repository.GetAsync(s => s.Id == id);
+
+ return student?.AsEntity();
+ }
+ public Task ExistsAsync(Guid id) => _repository.ExistsAsync(s => s.Id == id);
+ public Task AddAsync(Student student) => _repository.AddAsync(student.AsDocument());
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Services/DateTimeProvider.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Services/DateTimeProvider.cs
new file mode 100644
index 000000000..770cad0eb
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Services/DateTimeProvider.cs
@@ -0,0 +1,9 @@
+using MiniSpace.Services.Reports.Application.Services;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Services
+{
+ internal sealed class DateTimeProvider : IDateTimeProvider
+ {
+ public DateTime Now => DateTime.UtcNow;
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Services/EventMapper.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Services/EventMapper.cs
new file mode 100644
index 000000000..f171cdbe5
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Services/EventMapper.cs
@@ -0,0 +1,23 @@
+using Convey.CQRS.Events;
+using MiniSpace.Services.Reports.Application.Services;
+using MiniSpace.Services.Reports.Core;
+using MiniSpace.Services.Reports.Core.Events;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Services
+{
+ public class EventMapper : IEventMapper
+ {
+ public IEnumerable MapAll(IEnumerable events)
+ => events.Select(Map);
+
+ public IEvent Map(IDomainEvent @event)
+ {
+ switch (@event)
+ {
+
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Services/MessageBroker.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Services/MessageBroker.cs
new file mode 100644
index 000000000..a832264df
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Services/MessageBroker.cs
@@ -0,0 +1,84 @@
+using Convey.CQRS.Events;
+using Convey.MessageBrokers;
+using Convey.MessageBrokers.Outbox;
+using Convey.MessageBrokers.RabbitMQ;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using OpenTracing;
+using MiniSpace.Services.Reports.Application.Services;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Services
+{
+ internal sealed class MessageBroker : IMessageBroker
+ {
+ private const string DefaultSpanContextHeader = "span_context";
+ private readonly IBusPublisher _busPublisher;
+ private readonly IMessageOutbox _outbox;
+ private readonly ICorrelationContextAccessor _contextAccessor;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+ private readonly IMessagePropertiesAccessor _messagePropertiesAccessor;
+ private readonly ITracer _tracer;
+ private readonly ILogger _logger;
+ private readonly string _spanContextHeader;
+
+ public MessageBroker(IBusPublisher busPublisher, IMessageOutbox outbox,
+ ICorrelationContextAccessor contextAccessor, IHttpContextAccessor httpContextAccessor,
+ IMessagePropertiesAccessor messagePropertiesAccessor, RabbitMqOptions options, ITracer tracer,
+ ILogger logger)
+ {
+ _busPublisher = busPublisher;
+ _outbox = outbox;
+ _contextAccessor = contextAccessor;
+ _httpContextAccessor = httpContextAccessor;
+ _messagePropertiesAccessor = messagePropertiesAccessor;
+ _tracer = tracer;
+ _logger = logger;
+ _spanContextHeader = string.IsNullOrWhiteSpace(options.SpanContextHeader)
+ ? DefaultSpanContextHeader
+ : options.SpanContextHeader;
+ }
+
+ public Task PublishAsync(params IEvent[] events) => PublishAsync(events?.AsEnumerable());
+
+ public async Task PublishAsync(IEnumerable events)
+ {
+ if (events is null)
+ {
+ return;
+ }
+
+ var messageProperties = _messagePropertiesAccessor.MessageProperties;
+ var originatedMessageId = messageProperties?.MessageId;
+ var correlationId = messageProperties?.CorrelationId;
+ var spanContext = messageProperties?.GetSpanContext(_spanContextHeader);
+ if (string.IsNullOrWhiteSpace(spanContext))
+ {
+ spanContext = _tracer.ActiveSpan is null ? string.Empty : _tracer.ActiveSpan.Context.ToString();
+ }
+
+ var headers = messageProperties.GetHeadersToForward();
+ var correlationContext = _contextAccessor.CorrelationContext ??
+ _httpContextAccessor.GetCorrelationContext();
+
+ foreach (var @event in events)
+ {
+ if (@event is null)
+ {
+ continue;
+ }
+
+ var messageId = Guid.NewGuid().ToString("N");
+ _logger.LogTrace($"Publishing integration event: {@event.GetType().Name} [id: '{messageId}'].");
+ if (_outbox.Enabled)
+ {
+ await _outbox.SendAsync(@event, originatedMessageId, messageId, correlationId, spanContext,
+ correlationContext, headers);
+ continue;
+ }
+
+ await _busPublisher.PublishAsync(@event, messageId, correlationId, spanContext, correlationContext,
+ headers);
+ }
+ }
+ }
+}
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Services/ReportValidator.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Services/ReportValidator.cs
new file mode 100644
index 000000000..b33c7cb56
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Services/ReportValidator.cs
@@ -0,0 +1,62 @@
+using MiniSpace.Services.Reports.Application.Exceptions;
+using MiniSpace.Services.Reports.Application.Services;
+using MiniSpace.Services.Reports.Core.Entities;
+using MiniSpace.Services.Reports.Core.Exceptions;
+using InvalidReportStateException = MiniSpace.Services.Reports.Application.Exceptions.InvalidReportStateException;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Services
+{
+ public class ReportValidator : IReportValidator
+ {
+ private const int MaxActiveReports = 3;
+ private const int MaxReasonLength = 1000;
+
+ public ContextType ParseContextType(string contextType)
+ {
+ if (!Enum.TryParse(contextType, true, out var parsedContextType))
+ {
+ throw new InvalidContextTypeException(contextType);
+ }
+
+ return parsedContextType;
+ }
+
+ public ReportCategory ParseCategory(string category)
+ {
+ if (!Enum.TryParse(category, true, out var parsedCategory))
+ {
+ throw new InvalidReportCategoryException(category);
+ }
+
+ return parsedCategory;
+ }
+
+ public ReportState ParseStatus(string status)
+ {
+ if (!Enum.TryParse(status, true, out var parsedStatus))
+ {
+ throw new InvalidReportStateException(status);
+ }
+
+ return parsedStatus;
+ }
+
+ public void ValidateActiveReports(int activeReports)
+ {
+ if (activeReports >= MaxActiveReports)
+ {
+ throw new StudentTooManyActiveReportsException(activeReports, MaxActiveReports);
+ }
+ }
+
+ public void ValidateReason(string reason)
+ {
+ if (string.IsNullOrWhiteSpace(reason) || reason.Length > MaxReasonLength)
+ {
+ throw new InvalidReasonException(reason);
+ }
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Services/ReportsService.cs b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Services/ReportsService.cs
new file mode 100644
index 000000000..771e6b97b
--- /dev/null
+++ b/MiniSpace.Services.Reports/src/MiniSpace.Services.Reports.Infrastructure/Services/ReportsService.cs
@@ -0,0 +1,58 @@
+using MiniSpace.Services.Reports.Application;
+using MiniSpace.Services.Reports.Application.Commands;
+using MiniSpace.Services.Reports.Application.DTO;
+using MiniSpace.Services.Reports.Application.Exceptions;
+using MiniSpace.Services.Reports.Application.Services;
+using MiniSpace.Services.Reports.Core.Entities;
+using MiniSpace.Services.Reports.Core.Repositories;
+using MiniSpace.Services.Reports.Core.Wrappers;
+
+namespace MiniSpace.Services.Reports.Infrastructure.Services
+{
+ public class ReportsService : IReportsService
+ {
+ private readonly IReportRepository _reportRepository;
+ private readonly IReportValidator _reportValidator;
+ private readonly IAppContext _appContext;
+
+ public ReportsService(IReportRepository reportRepository, IReportValidator reportValidator, IAppContext appContext)
+ {
+ _reportRepository = reportRepository;
+ _reportValidator = reportValidator;
+ _appContext = appContext;
+ }
+
+ public async Task>> BrowseReportsAsync(SearchReports command)
+ {
+ var identity = _appContext.Identity;
+ if (identity.IsAuthenticated && !identity.IsAdmin)
+ {
+ throw new UnauthorizedReportSearchAttemptException(identity.Id, identity.Role);
+ }
+
+ var contextTypes = new List();
+ foreach (var contextType in command.ContextTypes)
+ {
+ contextTypes.Add(_reportValidator.ParseContextType(contextType));
+ }
+
+ var states = new List();
+ foreach (var status in command.States)
+ {
+ states.Add(_reportValidator.ParseStatus(status));
+ }
+
+ var pageNumber = command.Pageable.Page < 1 ? 1 : command.Pageable.Page;
+ var pageSize = command.Pageable.Size > 10 ? 10 : command.Pageable.Size;
+
+ var result = await _reportRepository.BrowseReportsAsync(pageNumber, pageSize,
+ contextTypes, states, command.ReviewerId, command.Pageable.Sort.SortBy, command.Pageable.Sort.Direction);
+
+ var pagedReports = new PagedResponse>(
+ result.reports.Select(r => new ReportDto(r)),
+ result.pageNumber, result.pageSize, result.totalPages, result.totalElements);
+
+ return pagedReports;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Reports/IReportsService.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Reports/IReportsService.cs
new file mode 100644
index 000000000..8772f115c
--- /dev/null
+++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Reports/IReportsService.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using MiniSpace.Web.DTO;
+using MiniSpace.Web.DTO.Wrappers;
+using MiniSpace.Web.HttpClients;
+
+namespace MiniSpace.Web.Areas.Reports
+{
+ public interface IReportsService
+ {
+ Task>>> SearchReportsAsync(
+ IEnumerable contextTypes, IEnumerable states, Guid reviewerId, PageableDto pageable);
+
+ Task> CreateReportAsync(Guid reportId, Guid issuerId, Guid targetId, Guid targetOwnerId,
+ string contextType, string category, string reason);
+
+ Task DeleteReportAsync(Guid reportId);
+
+ Task> CancelReportAsync(Guid reportId);
+
+ Task> StartReportReviewAsync(Guid reportId, Guid reviewerId);
+
+ Task> ResolveReportAsync(Guid reportId);
+
+ Task> RejectReportAsync(Guid reportId);
+
+ Task>>> GetStudentReports(Guid studentId, int page,
+ int results);
+ }
+}
\ No newline at end of file
diff --git a/MiniSpace.Web/src/MiniSpace.Web/Areas/Reports/ReportsService.cs b/MiniSpace.Web/src/MiniSpace.Web/Areas/Reports/ReportsService.cs
new file mode 100644
index 000000000..0cdfaeb07
--- /dev/null
+++ b/MiniSpace.Web/src/MiniSpace.Web/Areas/Reports/ReportsService.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using MiniSpace.Web.Areas.Identity;
+using MiniSpace.Web.Data.Reports;
+using MiniSpace.Web.DTO;
+using MiniSpace.Web.DTO.Wrappers;
+using MiniSpace.Web.HttpClients;
+
+namespace MiniSpace.Web.Areas.Reports
+{
+ public class ReportsService: IReportsService
+ {
+ private readonly IHttpClient _httpClient;
+ private readonly IIdentityService _identityService;
+
+ public ReportsService(IHttpClient httpClient, IIdentityService identityService)
+ {
+ _httpClient = httpClient;
+ _identityService = identityService;
+ }
+
+ public Task>>> SearchReportsAsync(
+ IEnumerable contextTypes, IEnumerable states, Guid reviewerId, PageableDto pageable)
+ {
+ return _httpClient.PostAsync>>("reports/search",
+ new (contextTypes, states, reviewerId, pageable));
+ }
+
+ public Task> CreateReportAsync(Guid reportId, Guid issuerId, Guid targetId, Guid targetOwnerId,
+ string contextType, string category, string reason)
+ {
+ _httpClient.SetAccessToken(_identityService.JwtDto.AccessToken);
+ return _httpClient.PostAsync