Skip to content
Merged
6 changes: 6 additions & 0 deletions backend/permissions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,12 @@ groups:
CountryProfiles:
description: Generate country profiles report
roles: [cce-super-admin, cce-admin]
UserPreferences:
description: Generate user-preference report
roles: [cce-super-admin, cce-admin]
Experts:
description: Generate expert registration report
roles: [cce-super-admin, cce-admin]
InteractiveMap:
Manage:
description: Create/update/delete interactive maps
Expand Down
108 changes: 108 additions & 0 deletions backend/src/CCE.Api.Internal/Endpoints/ReportEndpoints.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
using CCE.Api.Common.Extensions;
using CCE.Application.Reports;
using CCE.Application.Reports.Queries.GetCommunityPostReport;
using CCE.Application.Reports.Queries.GetCountryProfilesReport;
using CCE.Application.Reports.Queries.GetEventsReport;
using CCE.Application.Reports.Queries.GetExpertReport;
using CCE.Application.Reports.Queries.GetResourcesReport;
using CCE.Application.Reports.Queries.GetNewsReport;
using CCE.Application.Reports.Queries.GetSatisfactionSurveyReport;
using CCE.Application.Reports.Queries.GetUserPreferenceReport;
using CCE.Application.Reports.Queries.GetUserRegistrationReport;
using CCE.Domain;
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
Expand Down Expand Up @@ -148,6 +159,103 @@ public static IEndpointRouteBuilder MapReportEndpoints(this IEndpointRouteBuilde
.RequireAuthorization(Permissions.Report_CountryProfiles)
.WithName("CountryProfilesReport");

reports.MapGet("/user-registration", async (ISender sender) =>
{
var result = await sender.Send(new GetUserRegistrationReportQuery());
return result.ToHttpResult();
})
.RequireAuthorization(Permissions.Report_UserRegistrations)
.WithName("UserRegistrationReport");

reports.MapGet("/satisfaction-survey", async (ISender sender) =>
{
var result = await sender.Send(new GetSatisfactionSurveyReportQuery());
return result.ToHttpResult();
})
.RequireAuthorization(Permissions.Report_SatisfactionSurvey)
.WithName("SatisfactionSurveyReportJson");

reports.MapGet("/user-preferences", async (ISender sender) =>
{
var result = await sender.Send(new GetUserPreferenceReportQuery());
return result.ToHttpResult();
})
.RequireAuthorization(Permissions.Report_UserPreferences)
.WithName("UserPreferenceReport");

reports.MapGet("/experts", async (ISender sender) =>
{
var result = await sender.Send(new GetExpertReportQuery());
return result.ToHttpResult();
})
.RequireAuthorization(Permissions.Report_Experts)
.WithName("ExpertReport");

reports.MapGet("/news", async (
ISender sender,
DateTimeOffset? from,
DateTimeOffset? to,
int page = 1,
int pageSize = 20) =>
{
var result = await sender.Send(new GetNewsReportQuery(from, to, page, pageSize));
return result.ToHttpResult();
})
.RequireAuthorization(Permissions.Report_News)
.WithName("NewsReportJson");

reports.MapGet("/community-posts", async (
ISender sender,
DateTimeOffset? from,
DateTimeOffset? to,
int page = 1,
int pageSize = 20) =>
{
var result = await sender.Send(new GetCommunityPostReportQuery(from, to, page, pageSize));
return result.ToHttpResult();
})
.RequireAuthorization(Permissions.Report_CommunityPosts)
.WithName("CommunityPostReportJson");

reports.MapGet("/events", async (
ISender sender,
DateTimeOffset? from,
DateTimeOffset? to,
int page = 1,
int pageSize = 20) =>
{
var result = await sender.Send(new GetEventsReportQuery(from, to, page, pageSize));
return result.ToHttpResult();
})
.RequireAuthorization(Permissions.Report_Events)
.WithName("EventsReportJson");

reports.MapGet("/resources", async (
ISender sender,
DateTimeOffset? from,
DateTimeOffset? to,
int page = 1,
int pageSize = 20) =>
{
var result = await sender.Send(new GetResourcesReportQuery(from, to, page, pageSize));
return result.ToHttpResult();
})
.RequireAuthorization(Permissions.Report_Resources)
.WithName("ResourcesReportJson");

reports.MapGet("/country-profiles", async (
ISender sender,
DateTimeOffset? from,
DateTimeOffset? to,
int page = 1,
int pageSize = 20) =>
{
var result = await sender.Send(new GetCountryProfilesReportQuery(from, to, page, pageSize));
return result.ToHttpResult();
})
.RequireAuthorization(Permissions.Report_CountryProfiles)
.WithName("CountryProfilesReportJson");

return app;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace CCE.Application.Reports.Dtos;

public sealed record CommunityPostReportDto(
Guid Id,
string? PostTitle,
string? PostContent,
int PostType,
DateTimeOffset CreatedAt
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace CCE.Application.Reports.Dtos;

public sealed record CountryProfilesReportDto(
Guid Id,
string CountryName,
int? Population,
decimal? Area,
decimal? GdpPerCapita,
string? NdcAttachmentUrl,
string? CceClassification,
decimal? CcePerformanceIndex
);
14 changes: 14 additions & 0 deletions backend/src/CCE.Application/Reports/Dtos/EventsReportDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace CCE.Application.Reports.Dtos;

public sealed record EventsReportDto(
Guid Id,
string Title,
string EventDescription,
string? Location,
string Topic,
DateTimeOffset StartsOn,
DateTimeOffset EndsOn,
string? FeaturedImageUrl,
string? OnlineMeetingUrl,
DateTimeOffset CreatedAt
);
18 changes: 18 additions & 0 deletions backend/src/CCE.Application/Reports/Dtos/ExpertReportDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace CCE.Application.Reports.Dtos;

public sealed record ExpertReportDto(
Guid Id,
Guid UserId,
string FirstName,
string LastName,
string? Email,
string JobTitle,
string OrganizationName,
string CvDescriptionEn,
string CvDescriptionAr,
string? CvAttachmentUrl,
string CvFileFormat,
List<string> ExpertiseTopics,
int Status,
DateTimeOffset SubmittedAt
);
13 changes: 13 additions & 0 deletions backend/src/CCE.Application/Reports/Dtos/NewsReportDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace CCE.Application.Reports.Dtos;

public sealed record NewsReportDto(
Guid Id,
string TitleAr,
string TitleEn,
string? ImageUrl,
string TopicNameAr,
string TopicNameEn,
string ContentAr,
string ContentEn,
DateTimeOffset? PublishedAt
);
12 changes: 12 additions & 0 deletions backend/src/CCE.Application/Reports/Dtos/ResourcesReportDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace CCE.Application.Reports.Dtos;

public sealed record ResourcesReportDto(
Guid Id,
string Title,
string Description,
Guid CategoryId,
string Category,
int PostType,
Guid[] CoveredCountries,
DateTimeOffset CreatedAt
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace CCE.Application.Reports.Dtos;

public sealed record SatisfactionSurveyReportDto(
Guid Id,
int OverallSatisfaction,
int EaseOfUse,
int ContentSuitability,
string Feedback,
Guid? UserId,
DateTimeOffset SubmittedAt
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace CCE.Application.Reports.Dtos;

public sealed record UserPreferenceReportDto(
Guid Id,
List<Guid> AreasOfInterest,
int KnowledgeLevel,
string SectorOfWork,
Guid? CountryId
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace CCE.Application.Reports.Dtos;

public sealed record UserRegistrationReportUserDto(
Guid Id,
string FirstName,
string LastName,
string? Email,
string JobTitle,
string OrganizationName,
string? PhoneNumber
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using CCE.Application.Common;
using CCE.Application.Common.Pagination;
using CCE.Application.Reports.Dtos;
using MediatR;

namespace CCE.Application.Reports.Queries.GetCommunityPostReport;

public sealed record GetCommunityPostReportQuery(
DateTimeOffset? From = null,
DateTimeOffset? To = null,
int Page = 1,
int PageSize = 20
) : IRequest<Response<PagedResult<CommunityPostReportDto>>>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using CCE.Application.Common;
using CCE.Application.Common.Interfaces;
using CCE.Application.Common.Pagination;
using CCE.Application.Messages;
using CCE.Application.Reports.Dtos;
using MediatR;

namespace CCE.Application.Reports.Queries.GetCommunityPostReport;

internal sealed class GetCommunityPostReportQueryHandler(
ICceDbContext _db,
MessageFactory _msg)
: IRequestHandler<GetCommunityPostReportQuery, Response<PagedResult<CommunityPostReportDto>>>
{
public async Task<Response<PagedResult<CommunityPostReportDto>>> Handle(
GetCommunityPostReportQuery q, CancellationToken ct)
{
var query = _db.Posts.AsQueryable();

if (q.From.HasValue)
query = query.Where(p => p.CreatedOn >= q.From.Value);
if (q.To.HasValue)
query = query.Where(p => p.CreatedOn <= q.To.Value);

query = query.OrderByDescending(p => p.CreatedOn);

var paged = await query.ToPagedResultAsync(
p => new CommunityPostReportDto(
p.Id,
p.Title,
p.Content,
(int)p.Type,
p.CreatedOn),
q.Page,
q.PageSize,
ct)
.ConfigureAwait(false);

return _msg.Ok(paged, MessageKeys.General.ITEMS_LISTED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using CCE.Application.Common;
using CCE.Application.Common.Pagination;
using CCE.Application.Reports.Dtos;
using MediatR;

namespace CCE.Application.Reports.Queries.GetCountryProfilesReport;

public sealed record GetCountryProfilesReportQuery(
DateTimeOffset? From = null,
DateTimeOffset? To = null,
int Page = 1,
int PageSize = 20
) : IRequest<Response<PagedResult<CountryProfilesReportDto>>>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using CCE.Application.Common;
using CCE.Application.Common.Interfaces;
using CCE.Application.Common.Pagination;
using CCE.Application.Messages;
using CCE.Application.Reports.Dtos;
using MediatR;

namespace CCE.Application.Reports.Queries.GetCountryProfilesReport;

internal sealed class GetCountryProfilesReportQueryHandler(
ICceDbContext _db,
MessageFactory _msg)
: IRequestHandler<GetCountryProfilesReportQuery, Response<PagedResult<CountryProfilesReportDto>>>
{
public async Task<Response<PagedResult<CountryProfilesReportDto>>> Handle(
GetCountryProfilesReportQuery q, CancellationToken ct)
{
var query = from c in _db.Countries.WithoutSoftDeleteFilter()
where c.IsCceCountry
join p in _db.CountryProfiles on c.Id equals p.CountryId into pJoin
from p in pJoin.DefaultIfEmpty()
join asset in _db.AssetFiles on p.NationallyDeterminedContributionAssetId equals asset.Id into assetJoin
from asset in assetJoin.DefaultIfEmpty()
join snap in _db.CountryKapsarcSnapshots on c.LatestKapsarcSnapshotId equals snap.Id into snapJoin
from snap in snapJoin.DefaultIfEmpty()
select new
{
c,
p,
NdcUrl = (string?)asset.Url,
snap
};

if (q.From.HasValue)
query = query.Where(x => x.p != null && (x.p.LastModifiedOn ?? x.p.CreatedOn) >= q.From.Value);
if (q.To.HasValue)
query = query.Where(x => x.p != null && (x.p.LastModifiedOn ?? x.p.CreatedOn) <= q.To.Value);

query = query.OrderBy(x => x.c.NameEn);

var paged = await query.ToPagedResultAsync(
x => new CountryProfilesReportDto(
x.p != null ? x.p.Id : x.c.Id,
x.c.NameEn,
x.p != null ? x.p.Population : null,
x.p != null ? x.p.AreaSqKm : null,
x.p != null ? x.p.GdpPerCapita : null,
x.NdcUrl,
x.snap != null ? x.snap.Classification : null,
x.snap != null ? x.snap.PerformanceScore : null
),
q.Page,
q.PageSize,
ct)
.ConfigureAwait(false);

return _msg.Ok(paged, MessageKeys.General.ITEMS_LISTED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using CCE.Application.Common;
using CCE.Application.Common.Pagination;
using CCE.Application.Reports.Dtos;
using MediatR;

namespace CCE.Application.Reports.Queries.GetEventsReport;

public sealed record GetEventsReportQuery(
DateTimeOffset? From = null,
DateTimeOffset? To = null,
int Page = 1,
int PageSize = 20
) : IRequest<Response<PagedResult<EventsReportDto>>>;
Loading