diff --git a/src/Domain/Entities/Github/GithubProfileData.cs b/src/Domain/Entities/Github/GithubProfileData.cs
index 8ec69599..60cbda3c 100644
--- a/src/Domain/Entities/Github/GithubProfileData.cs
+++ b/src/Domain/Entities/Github/GithubProfileData.cs
@@ -46,6 +46,8 @@ public record GithubProfileData
public int MonthsToFetchCommits { get; init; }
+ public DateTime UserCreatedAt { get; init; }
+
public DateTime CreatedAt { get; init; }
public GithubProfileData()
@@ -105,6 +107,7 @@ public string GetTelegramFormattedText()
var result = $"Github stats for {Username}\n" +
$"Name: {Name}\n" +
+ $"Profile created: {UserCreatedAt:yyyy-MM-dd}\n" +
$"Profile URL\n\n" +
$"🌟 Social stats:\n" +
$"- Followers: {Followers:N0}\n" +
diff --git a/src/Infrastructure/Services/Github/GithubGraphQLService.cs b/src/Infrastructure/Services/Github/GithubGraphQLService.cs
index 6703a8f8..4cb598d3 100644
--- a/src/Infrastructure/Services/Github/GithubGraphQLService.cs
+++ b/src/Infrastructure/Services/Github/GithubGraphQLService.cs
@@ -366,7 +366,8 @@ private GithubProfileData MapToGithubProfileData(
user.ContributionsCollection?.CommitContributionsByRepository,
topLanguagesCount),
MonthsToFetchCommits = monthsToFetchCommits,
- CreatedAt = user.CreatedAt
+ UserCreatedAt = user.CreatedAt,
+ CreatedAt = DateTime.UtcNow,
};
}
@@ -487,10 +488,15 @@ public record UserProfile
public string Id { get; set; }
public string Name { get; set; }
public string Login { get; set; }
+
public string Url { get; set; }
+
public CountInfo Followers { get; set; }
+
public CountInfo Following { get; set; }
+
public CountInfo Repositories { get; set; }
+
public CountInfo StarredRepositories { get; set; }
public CountInfo Issues { get; set; }
public CountInfo PullRequests { get; set; }
diff --git a/src/Web.Api/Features/Github/DeleteGithubProcessingJob/DeleteGithubProcessingJobCommand.cs b/src/Web.Api/Features/Github/DeleteGithubProcessingJob/DeleteGithubProcessingJobCommand.cs
new file mode 100644
index 00000000..b75fedf3
--- /dev/null
+++ b/src/Web.Api/Features/Github/DeleteGithubProcessingJob/DeleteGithubProcessingJobCommand.cs
@@ -0,0 +1,5 @@
+namespace Web.Api.Features.Github.DeleteGithubProcessingJob;
+
+#pragma warning disable SA1313
+public record DeleteGithubProcessingJobCommand(
+ string Username);
\ No newline at end of file
diff --git a/src/Web.Api/Features/Github/DeleteGithubProcessingJob/DeleteGithubProcessingJobHandler.cs b/src/Web.Api/Features/Github/DeleteGithubProcessingJob/DeleteGithubProcessingJobHandler.cs
new file mode 100644
index 00000000..33e479b8
--- /dev/null
+++ b/src/Web.Api/Features/Github/DeleteGithubProcessingJob/DeleteGithubProcessingJobHandler.cs
@@ -0,0 +1,36 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Infrastructure.Database;
+using Infrastructure.Services.Mediator;
+using Microsoft.AspNetCore.Http;
+using Microsoft.EntityFrameworkCore;
+
+namespace Web.Api.Features.Github.DeleteGithubProcessingJob;
+
+public class DeleteGithubProcessingJobHandler : IRequestHandler
+{
+ private readonly DatabaseContext _context;
+
+ public DeleteGithubProcessingJobHandler(DatabaseContext context)
+ {
+ _context = context;
+ }
+
+ public async Task Handle(
+ DeleteGithubProcessingJobCommand request,
+ CancellationToken cancellationToken)
+ {
+ var job = await _context.GithubProfileProcessingJobs
+ .FirstOrDefaultAsync(x => x.Username == request.Username, cancellationToken);
+
+ if (job == null)
+ {
+ throw new BadHttpRequestException($"Github processing job with username '{request.Username}' not found");
+ }
+
+ _context.GithubProfileProcessingJobs.Remove(job);
+ await _context.TrySaveChangesAsync(cancellationToken);
+
+ return Nothing.Value;
+ }
+}
\ No newline at end of file
diff --git a/src/Web.Api/Features/Github/Dtos/GithubProfileBotChatDto.cs b/src/Web.Api/Features/Github/Dtos/GithubProfileBotChatDto.cs
new file mode 100644
index 00000000..89fbe5f9
--- /dev/null
+++ b/src/Web.Api/Features/Github/Dtos/GithubProfileBotChatDto.cs
@@ -0,0 +1,37 @@
+using System;
+using Domain.Entities.Github;
+
+namespace Web.Api.Features.Github.Dtos;
+
+public record GithubProfileBotChatDto
+{
+ public Guid Id { get; init; }
+
+ public long ChatId { get; init; }
+
+ public string Username { get; init; }
+
+ public bool IsAdmin { get; init; }
+
+ public int MessagesCount { get; init; }
+
+ public DateTimeOffset CreatedAt { get; init; }
+
+ public DateTimeOffset UpdatedAt { get; init; }
+
+ public GithubProfileBotChatDto()
+ {
+ }
+
+ public GithubProfileBotChatDto(
+ GithubProfileBotChat chat)
+ {
+ Id = chat.Id;
+ ChatId = chat.ChatId;
+ Username = chat.Username;
+ IsAdmin = chat.IsAdmin;
+ MessagesCount = chat.MessagesCount;
+ CreatedAt = chat.CreatedAt;
+ UpdatedAt = chat.UpdatedAt;
+ }
+}
\ No newline at end of file
diff --git a/src/Web.Api/Features/Github/Dtos/GithubProfileDto.cs b/src/Web.Api/Features/Github/Dtos/GithubProfileDto.cs
new file mode 100644
index 00000000..7dce8296
--- /dev/null
+++ b/src/Web.Api/Features/Github/Dtos/GithubProfileDto.cs
@@ -0,0 +1,34 @@
+using System;
+using Domain.Entities.Github;
+
+namespace Web.Api.Features.Github.Dtos;
+
+public record GithubProfileDto
+{
+ public string Username { get; init; }
+
+ public int Version { get; init; }
+
+ public int RequestsCount { get; init; }
+
+ public DateTime DataSyncedAt { get; init; }
+
+ public DateTimeOffset CreatedAt { get; init; }
+
+ public DateTimeOffset UpdatedAt { get; init; }
+
+ public GithubProfileDto()
+ {
+ }
+
+ public GithubProfileDto(
+ GithubProfile profile)
+ {
+ Username = profile.Username;
+ Version = profile.Version;
+ RequestsCount = profile.RequestsCount;
+ DataSyncedAt = profile.DataSyncedAt;
+ CreatedAt = profile.CreatedAt;
+ UpdatedAt = profile.UpdatedAt;
+ }
+}
\ No newline at end of file
diff --git a/src/Web.Api/Features/Github/Dtos/GithubProfileProcessingJobDto.cs b/src/Web.Api/Features/Github/Dtos/GithubProfileProcessingJobDto.cs
new file mode 100644
index 00000000..01394443
--- /dev/null
+++ b/src/Web.Api/Features/Github/Dtos/GithubProfileProcessingJobDto.cs
@@ -0,0 +1,24 @@
+using System;
+using Domain.Entities.Github;
+
+namespace Web.Api.Features.Github.Dtos;
+
+public record GithubProfileProcessingJobDto
+{
+ public string Username { get; init; }
+
+ public DateTimeOffset CreatedAt { get; init; }
+
+ public DateTimeOffset UpdatedAt { get; init; }
+
+ public GithubProfileProcessingJobDto()
+ {
+ }
+
+ public GithubProfileProcessingJobDto(GithubProfileProcessingJob job)
+ {
+ Username = job.Username;
+ CreatedAt = job.CreatedAt;
+ UpdatedAt = job.UpdatedAt;
+ }
+}
\ No newline at end of file
diff --git a/src/Web.Api/Features/Github/GetGithubProcessingJobs/GetGithubProcessingJobsHandler.cs b/src/Web.Api/Features/Github/GetGithubProcessingJobs/GetGithubProcessingJobsHandler.cs
new file mode 100644
index 00000000..18474569
--- /dev/null
+++ b/src/Web.Api/Features/Github/GetGithubProcessingJobs/GetGithubProcessingJobsHandler.cs
@@ -0,0 +1,31 @@
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Infrastructure.Database;
+using Infrastructure.Services.Mediator;
+using Microsoft.EntityFrameworkCore;
+using Web.Api.Features.Github.Dtos;
+
+namespace Web.Api.Features.Github.GetGithubProcessingJobs;
+
+public class GetGithubProcessingJobsHandler : IRequestHandler
+{
+ private readonly DatabaseContext _context;
+
+ public GetGithubProcessingJobsHandler(DatabaseContext context)
+ {
+ _context = context;
+ }
+
+ public async Task Handle(
+ Nothing request,
+ CancellationToken cancellationToken)
+ {
+ var jobs = await _context.GithubProfileProcessingJobs
+ .OrderByDescending(x => x.CreatedAt)
+ .ToListAsync(cancellationToken);
+
+ return new GetGithubProcessingJobsResponse(
+ jobs.Select(x => new GithubProfileProcessingJobDto(x)).ToList());
+ }
+}
\ No newline at end of file
diff --git a/src/Web.Api/Features/Github/GetGithubProcessingJobs/GetGithubProcessingJobsResponse.cs b/src/Web.Api/Features/Github/GetGithubProcessingJobs/GetGithubProcessingJobsResponse.cs
new file mode 100644
index 00000000..55bb5f5f
--- /dev/null
+++ b/src/Web.Api/Features/Github/GetGithubProcessingJobs/GetGithubProcessingJobsResponse.cs
@@ -0,0 +1,8 @@
+using System.Collections.Generic;
+using Web.Api.Features.Github.Dtos;
+
+namespace Web.Api.Features.Github.GetGithubProcessingJobs;
+
+#pragma warning disable SA1313
+public record GetGithubProcessingJobsResponse(
+ IReadOnlyCollection Results);
\ No newline at end of file
diff --git a/src/Web.Api/Features/Github/GetGithubProfileChats/GetGithubProfileChatsHandler.cs b/src/Web.Api/Features/Github/GetGithubProfileChats/GetGithubProfileChatsHandler.cs
new file mode 100644
index 00000000..2b7efa5a
--- /dev/null
+++ b/src/Web.Api/Features/Github/GetGithubProfileChats/GetGithubProfileChatsHandler.cs
@@ -0,0 +1,40 @@
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Infrastructure.Database;
+using Infrastructure.Services.Mediator;
+using Microsoft.EntityFrameworkCore;
+using Web.Api.Features.Github.Dtos;
+
+namespace Web.Api.Features.Github.GetGithubProfileChats;
+
+public class GetGithubProfileChatsHandler
+ : IRequestHandler
+{
+ private readonly DatabaseContext _context;
+
+ public GetGithubProfileChatsHandler(DatabaseContext context)
+ {
+ _context = context;
+ }
+
+ public async Task Handle(
+ GetGithubProfileChatsQueryParams request,
+ CancellationToken cancellationToken)
+ {
+ var chats = await _context.GithubProfileBotChats
+ .OrderByDescending(x => x.MessagesCount)
+ .ThenByDescending(x => x.CreatedAt)
+ .AsPaginatedAsync(
+ request,
+ cancellationToken);
+
+ return new GetGithubProfileChatsResponse(
+ chats.CurrentPage,
+ chats.PageSize,
+ chats.TotalItems,
+ chats.Results
+ .Select(x => new GithubProfileBotChatDto(x))
+ .ToList());
+ }
+}
\ No newline at end of file
diff --git a/src/Web.Api/Features/Github/GetGithubProfileChats/GetGithubProfileChatsQueryParams.cs b/src/Web.Api/Features/Github/GetGithubProfileChats/GetGithubProfileChatsQueryParams.cs
new file mode 100644
index 00000000..8b226319
--- /dev/null
+++ b/src/Web.Api/Features/Github/GetGithubProfileChats/GetGithubProfileChatsQueryParams.cs
@@ -0,0 +1,7 @@
+using Domain.ValueObjects.Pagination;
+
+namespace Web.Api.Features.Github.GetGithubProfileChats;
+
+public record GetGithubProfileChatsQueryParams : PageModel
+{
+}
\ No newline at end of file
diff --git a/src/Web.Api/Features/Github/GetGithubProfileChats/GetGithubProfileChatsResponse.cs b/src/Web.Api/Features/Github/GetGithubProfileChats/GetGithubProfileChatsResponse.cs
new file mode 100644
index 00000000..b299a5e9
--- /dev/null
+++ b/src/Web.Api/Features/Github/GetGithubProfileChats/GetGithubProfileChatsResponse.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+using Domain.ValueObjects.Pagination;
+using Web.Api.Features.Github.Dtos;
+
+namespace Web.Api.Features.Github.GetGithubProfileChats;
+
+#pragma warning disable SA1313
+public record GetGithubProfileChatsResponse(
+ int CurrentPage,
+ int PageSize,
+ int TotalItems,
+ IReadOnlyCollection Results)
+ : Pageable(CurrentPage, PageSize, TotalItems, Results);
\ No newline at end of file
diff --git a/src/Web.Api/Features/Github/GetGithubProfiles/GetGithubProfilesHandler.cs b/src/Web.Api/Features/Github/GetGithubProfiles/GetGithubProfilesHandler.cs
new file mode 100644
index 00000000..30bfa060
--- /dev/null
+++ b/src/Web.Api/Features/Github/GetGithubProfiles/GetGithubProfilesHandler.cs
@@ -0,0 +1,38 @@
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Infrastructure.Database;
+using Infrastructure.Services.Mediator;
+using Microsoft.EntityFrameworkCore;
+using Web.Api.Features.Github.Dtos;
+
+namespace Web.Api.Features.Github.GetGithubProfiles;
+
+public class GetGithubProfilesHandler : IRequestHandler
+{
+ private readonly DatabaseContext _context;
+
+ public GetGithubProfilesHandler(DatabaseContext context)
+ {
+ _context = context;
+ }
+
+ public async Task Handle(
+ GetGithubProfilesQueryParams request,
+ CancellationToken cancellationToken)
+ {
+ var profiles = await _context.GithubProfiles
+ .OrderByDescending(x => x.CreatedAt)
+ .AsPaginatedAsync(
+ request,
+ cancellationToken);
+
+ return new GetGithubProfilesResponse(
+ profiles.CurrentPage,
+ profiles.PageSize,
+ profiles.TotalItems,
+ profiles.Results
+ .Select(x => new GithubProfileDto(x))
+ .ToList());
+ }
+}
\ No newline at end of file
diff --git a/src/Web.Api/Features/Github/GetGithubProfiles/GetGithubProfilesQueryParams.cs b/src/Web.Api/Features/Github/GetGithubProfiles/GetGithubProfilesQueryParams.cs
new file mode 100644
index 00000000..b7ecf685
--- /dev/null
+++ b/src/Web.Api/Features/Github/GetGithubProfiles/GetGithubProfilesQueryParams.cs
@@ -0,0 +1,7 @@
+using Domain.ValueObjects.Pagination;
+
+namespace Web.Api.Features.Github.GetGithubProfiles;
+
+public record GetGithubProfilesQueryParams : PageModel
+{
+}
\ No newline at end of file
diff --git a/src/Web.Api/Features/Github/GetGithubProfiles/GetGithubProfilesResponse.cs b/src/Web.Api/Features/Github/GetGithubProfiles/GetGithubProfilesResponse.cs
new file mode 100644
index 00000000..725e7ef8
--- /dev/null
+++ b/src/Web.Api/Features/Github/GetGithubProfiles/GetGithubProfilesResponse.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+using Domain.ValueObjects.Pagination;
+using Web.Api.Features.Github.Dtos;
+
+namespace Web.Api.Features.Github.GetGithubProfiles;
+
+#pragma warning disable SA1313
+public record GetGithubProfilesResponse(
+ int CurrentPage,
+ int PageSize,
+ int TotalItems,
+ IReadOnlyCollection Results)
+ : Pageable(CurrentPage, PageSize, TotalItems, Results);
\ No newline at end of file
diff --git a/src/Web.Api/Features/Github/GithubController.cs b/src/Web.Api/Features/Github/GithubController.cs
new file mode 100644
index 00000000..7a74eab6
--- /dev/null
+++ b/src/Web.Api/Features/Github/GithubController.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Domain.Enums;
+using Infrastructure.Services.Mediator;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
+using Web.Api.Features.Github.DeleteGithubProcessingJob;
+using Web.Api.Features.Github.GetGithubProcessingJobs;
+using Web.Api.Features.Github.GetGithubProfileChats;
+using Web.Api.Features.Github.GetGithubProfiles;
+using Web.Api.Setup.Attributes;
+
+namespace Web.Api.Features.Github;
+
+[ApiController]
+[Route("api/github")]
+[HasAnyRole(Role.Admin)]
+public class GithubController : ControllerBase
+{
+ private readonly IServiceProvider _serviceProvider;
+
+ public GithubController(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ }
+
+ [HttpGet("profiles")]
+ public async Task GetGithubProfiles(
+ [FromQuery] GetGithubProfilesQueryParams queryParams,
+ CancellationToken cancellationToken)
+ {
+ return Ok(
+ await _serviceProvider.GetRequiredService()
+ .Handle(queryParams, cancellationToken));
+ }
+
+ [HttpGet("chats")]
+ public async Task GetGithubProfileChats(
+ [FromQuery] GetGithubProfileChatsQueryParams queryParams,
+ CancellationToken cancellationToken)
+ {
+ return Ok(
+ await _serviceProvider.GetRequiredService()
+ .Handle(queryParams, cancellationToken));
+ }
+
+ [HttpGet("processing-jobs")]
+ public async Task GetGithubProcessingJobs(
+ CancellationToken cancellationToken)
+ {
+ return Ok(
+ await _serviceProvider.GetRequiredService()
+ .Handle(Nothing.Value, cancellationToken));
+ }
+
+ [HttpDelete("processing-jobs/{username}")]
+ public async Task DeleteGithubProcessingJob(
+ string username,
+ CancellationToken cancellationToken)
+ {
+ try
+ {
+ await _serviceProvider.GetRequiredService()
+ .Handle(new DeleteGithubProcessingJobCommand(username), cancellationToken);
+
+ return Ok();
+ }
+ catch (BadHttpRequestException ex)
+ {
+ return NotFound(ex.Message);
+ }
+ }
+}
\ No newline at end of file