Final task implemented

This commit is contained in:
Egor 2022-09-01 02:07:28 +03:00
parent 4e4a0b442c
commit 57eaccb746
1322 changed files with 31526 additions and 1 deletions

25
FinalTask/.dockerignore Normal file
View file

@ -0,0 +1,25 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

57
FinalTask/FinalTask.sln Normal file
View file

@ -0,0 +1,57 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.3.32819.101
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileAPI", "MobileAPI\MobileAPI.csproj", "{1977554E-2340-4721-9142-122CBDDA8F29}"
EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{1E938B61-E2A1-4CD5-91DC-F07329CD5DAA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PushAPI", "PushAPI\PushAPI.csproj", "{F2BB9E8F-3A3C-470D-BF58-6172D55ACA08}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PushShared", "PushShared\PushShared.csproj", "{1DCD4394-2244-40F0-812A-C09600285058}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PushSender", "PushSender\PushSender.csproj", "{F4CEFE06-CA08-4A39-952B-BF4BCA0E8392}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StatsAPI", "StatsAPI\StatsAPI.csproj", "{5204DD72-EF3B-42EA-8A3A-D6C79B25218C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "postgres-data", "postgres-data", "{C4F6D0E0-339F-4D75-A1DB-8224A0EE658E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1977554E-2340-4721-9142-122CBDDA8F29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1977554E-2340-4721-9142-122CBDDA8F29}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1977554E-2340-4721-9142-122CBDDA8F29}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1977554E-2340-4721-9142-122CBDDA8F29}.Release|Any CPU.Build.0 = Release|Any CPU
{1E938B61-E2A1-4CD5-91DC-F07329CD5DAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1E938B61-E2A1-4CD5-91DC-F07329CD5DAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E938B61-E2A1-4CD5-91DC-F07329CD5DAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E938B61-E2A1-4CD5-91DC-F07329CD5DAA}.Release|Any CPU.Build.0 = Release|Any CPU
{F2BB9E8F-3A3C-470D-BF58-6172D55ACA08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2BB9E8F-3A3C-470D-BF58-6172D55ACA08}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2BB9E8F-3A3C-470D-BF58-6172D55ACA08}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2BB9E8F-3A3C-470D-BF58-6172D55ACA08}.Release|Any CPU.Build.0 = Release|Any CPU
{1DCD4394-2244-40F0-812A-C09600285058}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1DCD4394-2244-40F0-812A-C09600285058}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1DCD4394-2244-40F0-812A-C09600285058}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1DCD4394-2244-40F0-812A-C09600285058}.Release|Any CPU.Build.0 = Release|Any CPU
{F4CEFE06-CA08-4A39-952B-BF4BCA0E8392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F4CEFE06-CA08-4A39-952B-BF4BCA0E8392}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F4CEFE06-CA08-4A39-952B-BF4BCA0E8392}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F4CEFE06-CA08-4A39-952B-BF4BCA0E8392}.Release|Any CPU.Build.0 = Release|Any CPU
{5204DD72-EF3B-42EA-8A3A-D6C79B25218C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5204DD72-EF3B-42EA-8A3A-D6C79B25218C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5204DD72-EF3B-42EA-8A3A-D6C79B25218C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5204DD72-EF3B-42EA-8A3A-D6C79B25218C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FD5BC6D8-4B8B-4F36-8E31-65CC0FD31BAE}
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,53 @@
using Microsoft.AspNetCore.Mvc;
using PushShared;
using PushShared.Mobile.Data;
namespace MobileAPI.Controllers
{
[ApiController]
[Route("mobile_apps")]
public class MobileController : ControllerBase
{
private readonly ILogger<MobileController> _logger;
private readonly MobileContext _mobileContext;
public MobileController(ILogger<MobileController> logger, MobileContext mobileContext)
{
_logger = logger;
_mobileContext = mobileContext;
}
[HttpPost("register_app")]
public IActionResult RegisterUser(MobileAppUser user)
{
_logger.LogInformation("Registering a new user app...");
var exists = _mobileContext.MobileAppUsers.Any(u => u.AppGuid == user.AppGuid);
if (exists)
{
_logger.LogWarning("User app with GUID {AppGuid} already exists", user.AppGuid);
return BadRequest("User app with this GUID already exists");
}
_mobileContext.MobileAppUsers.Add(user);
_mobileContext.SaveChanges();
_logger.LogInformation("User app successfully registered");
return Ok();
}
[HttpDelete("delete_app")]
public IActionResult DeleteUser([GuidAttribue] string guid)
{
_logger.LogInformation("Deleteing a user app...");
var exists = _mobileContext.MobileAppUsers.Any(u => u.AppGuid == guid);
if (!exists)
{
_logger.LogWarning("User app with GUID {guid} was not found", guid);
return NotFound();
}
var userToDelete = _mobileContext.MobileAppUsers.First(u => u.AppGuid == guid);
_mobileContext.MobileAppUsers.Remove(userToDelete);
_mobileContext.SaveChanges();
_logger.LogInformation("User app successfully deleted");
return Ok();
}
}
}

View file

@ -0,0 +1,22 @@
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["MobileAPI/MobileAPI.csproj", "MobileAPI/"]
RUN dotnet restore "MobileAPI/MobileAPI.csproj"
COPY . .
WORKDIR "/src/MobileAPI"
RUN dotnet build "MobileAPI.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "MobileAPI.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MobileAPI.dll"]

View file

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>38a4d0c3-a019-452f-80db-b0e3876be206</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.0-preview.7.22376.2" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.0-preview.7" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PushShared\PushShared.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,27 @@
using PushShared;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddDbContext<MobileContext>();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

View file

@ -0,0 +1,38 @@
{
"profiles": {
"MobileAPI": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7165;http://localhost:5165"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"publishAllPorts": true,
"useSSL": true
}
},
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:36599",
"sslPort": 44354
}
}
}

View file

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View file

@ -0,0 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Postgres": "host=postgres-mobile;port=5432;database=mobile;username=postgres;password=postgres"
}
}

View file

@ -0,0 +1,56 @@
using Microsoft.AspNetCore.Mvc;
using PushShared;
using PushShared.Mobile.Data;
using PushShared.Push.Data;
namespace PushAPI.Controllers
{
[ApiController]
[Route("push")]
public class PushController : ControllerBase
{
private readonly ILogger<PushController> _logger;
private readonly MobileContext _context;
private readonly IPushService _pushService;
public PushController(ILogger<PushController> logger, MobileContext context, IPushService pushService)
{
_logger = logger;
_context = context;
_pushService = pushService;
}
[HttpPost("create")]
public IActionResult CreatePush(PushNotification push)
{
_logger.LogInformation("Received a request to create a new push notification...");
_logger.LogInformation("Title: {Title}", push.Title);
var guids = new List<string>();
_logger.LogInformation("Fetching GUIDs...");
foreach (var phone in push.SendToNumbers)
{
var guidsForPhone = _context.MobileAppUsers.Where(u => phone == u.Phone).Select(u => u.AppGuid);
if (!guidsForPhone.Any())
{
_logger.LogWarning("Phone number {phone} isn't registered in the database", phone);
continue;
}
guids.AddRange(guidsForPhone);
var msg = new Message() { Phone = phone, Title = push.Title, Contents = push.Message };
_context.Messages.Add(msg);
}
if (!guids.Any())
{
var error = "None of the specified numbers are registered in the database, push notification won't be sent";
_logger.LogWarning(error);
return NotFound(error);
}
_pushService.SendPush(push);
_logger.LogInformation("Successfully sent push notification to queue");
_logger.LogInformation("Saving sent messages to database...");
_context.SaveChanges();
_logger.LogInformation("Successfully saved the messages");
return Ok();
}
}
}

View file

@ -0,0 +1,22 @@
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["PushAPI/PushAPI.csproj", "PushAPI/"]
RUN dotnet restore "PushAPI/PushAPI.csproj"
COPY . .
WORKDIR "/src/PushAPI"
RUN dotnet build "PushAPI.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "PushAPI.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "PushAPI.dll"]

View file

@ -0,0 +1,28 @@
using PushShared;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddSingleton<IPushService, RabbitPushService>();
builder.Services.AddDbContext<MobileContext>();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

View file

@ -0,0 +1,38 @@
{
"profiles": {
"PushAPI": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7170;http://localhost:5170"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"publishAllPorts": true,
"useSSL": true
}
},
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:37638",
"sslPort": 44356
}
}
}

View file

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>cd44cefb-7a63-461e-b3af-94972ea9acdd</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PushShared\PushShared.csproj" />
</ItemGroup>
<ProjectExtensions><VisualStudio><UserProperties appsettings_1json__JsonSchema="" /></VisualStudio></ProjectExtensions>
</Project>

View file

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View file

@ -0,0 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Postgres": "host=postgres-mobile;port=5432;database=mobile;username=postgres;password=postgres",
"RabbitMQ": "amqp://guest:guest@rabbitmq:5672"
}
}

View file

@ -0,0 +1,20 @@
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["PushSender/PushSender.csproj", "PushSender/"]
RUN dotnet restore "PushSender/PushSender.csproj"
COPY . .
WORKDIR "/src/PushSender"
RUN dotnet build "PushSender.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "PushSender.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "PushSender.dll"]

View file

@ -0,0 +1,12 @@
using PushSender;
using PushShared;
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddSingleton<IPushService, RabbitPushService>();
services.AddHostedService<PushSenderWorker>();
})
.Build();
await host.RunAsync();

View file

@ -0,0 +1,14 @@
{
"profiles": {
"PushSender": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true
},
"Docker": {
"commandName": "Docker"
}
}
}

View file

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>dotnet-PushSender-536BE94D-F909-4337-AA5A-208060D4A368</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PushShared\PushShared.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,32 @@
using PushShared;
namespace PushSender
{
public class PushSenderWorker : BackgroundService
{
private readonly ILogger<PushSenderWorker> _logger;
private readonly IPushService _pushService;
public PushSenderWorker(ILogger<PushSenderWorker> logger, IPushService pushService)
{
_logger = logger;
_pushService = pushService;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Waiting for a push notification to send...");
while (!stoppingToken.IsCancellationRequested)
{
var push = _pushService.ReceivePush();
if (push is not null)
{
_logger.LogInformation("Sending push notifications with title {Title}...", push.Title);
await Task.Delay(100, stoppingToken);
_logger.LogInformation("Push notification successfully sent");
}
await Task.Delay(1000, stoppingToken);
}
}
}
}

View file

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View file

@ -0,0 +1,11 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ConnectionStrings": {
"RabbitMQ": "amqp://guest:guest@rabbitmq:5672"
}
}

View file

@ -0,0 +1,10 @@
using PushShared.Push.Data;
namespace PushShared
{
public interface IPushService
{
public void SendPush(PushNotification push);
public PushNotification? ReceivePush();
}
}

View file

@ -0,0 +1,30 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using PushShared.Mobile.Data;
namespace PushShared;
public class MobileContext : DbContext
{
public DbSet<MobileAppUser> MobileAppUsers { get; set; } = null!;
public DbSet<Message> Messages { get; set; } = null!;
private readonly ILogger<MobileContext> _logger;
private readonly IConfiguration _configuration;
public MobileContext(ILogger<MobileContext> logger, IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
_logger.LogInformation("Ensuring that the database exists...");
Database.EnsureCreated();
_logger.LogInformation("Success");
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
_logger.LogInformation("Connecting to database...");
optionsBuilder.UseNpgsql(_configuration.GetConnectionString("Postgres"));
_logger.LogInformation("Connection established");
}
}

View file

@ -0,0 +1,43 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using PushShared.Push.Data;
namespace PushShared.Mobile.Data
{
[Table("app_users")]
public class MobileAppUser
{
[Key, Column("id"), JsonIgnore]
public long Id { get; set; }
[Required, GuidAttribue, Column("app_guid")]
public string AppGuid { get; set; }
[Required, Phone, Column("phone")]
public string Phone { get; set; }
[Required, Column("version")]
public string Version { get; set; }
}
[Table("messages")]
public class Message
{
[Key, Column("id"), JsonIgnore]
public long Id { get; set; }
[Required, Column("title")]
public string Title { get; set; }
[Required, Column("contents")]
public string Contents { get; set; }
[Required, Column("phone"), JsonIgnore]
public string Phone { get; set; }
}
public class GuidAttribue : ValidationAttribute
{
public override bool IsValid(object? value)
{
if (value == null) return false;
if (value is not string guid) return false;
return Guid.TryParse(guid, out _);
}
}
}

View file

@ -0,0 +1,32 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace PushShared.Push.Data
{
public class PushNotification
{
[Required]
public string Title { get; set; }
[Required]
public string Message { get; set; }
[Required, EnumerablePhones]
public IEnumerable<string> SendToNumbers { get; set; }
[JsonIgnore]
public IEnumerable<string> SendToGuids { get; set; } = Enumerable.Empty<string>();
}
public class EnumerablePhonesAttribute : ValidationAttribute
{
public override bool IsValid(object? value)
{
if (value == null) return false;
if (value is not IEnumerable<string> phones) return false;
var phoneAttribute = new PhoneAttribute();
foreach (var phone in phones)
{
if (!phoneAttribute.IsValid(phone)) return false;
}
return true;
}
}
}

View file

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.0-preview.7.22376.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0-preview.7.22375.6" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.0-preview.7" />
<PackageReference Include="RabbitMQ.Client" Version="6.4.0" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,56 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using PushShared.Push.Data;
using RabbitMQ.Client;
using System.Text;
using System.Text.Json;
namespace PushShared
{
public class RabbitPushService : IPushService
{
private readonly ILogger<RabbitPushService> _logger;
private readonly IConfiguration _configuration;
private readonly ConnectionFactory _connectionFactory;
private readonly IConnection _connection;
private readonly IModel _channel;
public RabbitPushService(ILogger<RabbitPushService> logger, IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
_connectionFactory = new() { Uri = new(_configuration.GetConnectionString("RabbitMQ")!) };
_connection = _connectionFactory.CreateConnection();
_channel = _connection.CreateModel();
_channel.QueueDeclare(queue: "push-queue",
durable: false,
exclusive: false,
autoDelete: false);
}
public void SendPush(PushNotification push)
{
_logger.LogInformation("Serializing push notification...");
var body = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(push));
_logger.LogInformation("Publishing push notification to queue");
_channel.BasicPublish(exchange: "",
routingKey: "push-queue",
body: body);
_logger.LogInformation("Publish successful");
}
public PushNotification? ReceivePush()
{
var result = _channel.BasicGet("push-queue", true);
if (result is null)
{
return null;
}
_logger.LogInformation("Push notification found, deserializing...");
var body = result.Body.ToArray();
var push = JsonSerializer.Deserialize<PushNotification>(Encoding.UTF8.GetString(body));
_logger.LogInformation("Get successful, returning push notification");
return push;
}
}
}

View file

@ -0,0 +1,61 @@
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using PushShared;
using PushShared.Mobile.Data;
using System.ComponentModel.DataAnnotations;
namespace StatsAPI.Controllers
{
[ApiController]
[Route("stats")]
public class StatsController : ControllerBase
{
private readonly ILogger<StatsController> _logger;
private readonly MobileContext _context;
public StatsController(ILogger<StatsController> logger, MobileContext context)
{
_logger = logger;
_context = context;
}
[HttpGet("user_apps")]
public IActionResult GetUserApps()
{
_logger.LogInformation("Fetching the user apps data...");
var userAppStats = new
{
AppUsers = new List<MobileAppUser>(),
VersionRegistrations = new Dictionary<string, int>(),
VersionUniquePhones = new Dictionary<string, int>()
};
_logger.LogInformation("Getting all user apps from databse...");
var appUsers = _context.MobileAppUsers.Select(u => u).AsEnumerable();
userAppStats.AppUsers.AddRange(appUsers);
_logger.LogInformation("Calculating registrations and unique phone numbers for each version...");
var versions = appUsers.Select(u => u.Version).ToHashSet();
foreach (var version in versions)
{
userAppStats.VersionRegistrations[version] = appUsers.Count(u => u.Version == version);
userAppStats.VersionUniquePhones[version] = appUsers.DistinctBy(u => u.Phone).Count(u => u.Version == version);
}
_logger.LogInformation("Successfully fetched the data");
return Ok(userAppStats);
}
[HttpGet("messages")]
public IActionResult GetMessages([Phone] string phone)
{
_logger.LogInformation("Fetching the messages for phone number {phone}...", phone);
var messageStats = new { Messages = new List<Message>() };
messageStats.Messages.AddRange(_context.Messages.Where(m => m.Phone == phone).Select(m => m));
if (!messageStats.Messages.Any())
{
_logger.LogWarning("Phone number {phone} doesn't have any messages", phone);
return NotFound("The specefied number hasn't received any push notifications yet");
}
_logger.LogInformation("Successfully fetched the messages");
return Ok(messageStats);
}
}
}

View file

@ -0,0 +1,22 @@
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["StatsAPI/StatsAPI.csproj", "StatsAPI/"]
RUN dotnet restore "StatsAPI/StatsAPI.csproj"
COPY . .
WORKDIR "/src/StatsAPI"
RUN dotnet build "StatsAPI.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "StatsAPI.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "StatsAPI.dll"]

View file

@ -0,0 +1,34 @@
using PushShared;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddDbContext<MobileContext>();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseCors(options =>
{
options.AllowAnyOrigin();
options.AllowAnyMethod();
options.AllowAnyHeader();
});
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

View file

@ -0,0 +1,38 @@
{
"profiles": {
"StatsAPI": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7182;http://localhost:5182"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"publishAllPorts": true,
"useSSL": true
}
},
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:40342",
"sslPort": 44360
}
}
}

View file

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>8c7ea8a2-6f18-482c-a3e4-3320f3f47843</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PushShared\PushShared.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View file

@ -0,0 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Postgres": "host=postgres-mobile;port=5432;database=mobile;username=postgres;password=postgres"
}
}

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk">
<PropertyGroup Label="Globals">
<ProjectVersion>2.1</ProjectVersion>
<DockerTargetOS>Linux</DockerTargetOS>
<ProjectGuid>1e938b61-e2a1-4cd5-91dc-f07329cd5daa</ProjectGuid>
<DockerLaunchAction>LaunchBrowser</DockerLaunchAction>
<DockerServiceUrl>{Scheme}://localhost:{ServicePort}/swagger</DockerServiceUrl>
<DockerServiceName>mobileapi</DockerServiceName>
</PropertyGroup>
<ItemGroup>
<None Include="docker-compose.override.yml">
<DependentUpon>docker-compose.yml</DependentUpon>
</None>
<None Include="docker-compose.yml" />
<None Include=".dockerignore" />
</ItemGroup>
<ProjectExtensions>
<VisualStudio>
<UserProperties launchsettings_1json__JsonSchema="" />
</VisualStudio>
</ProjectExtensions>
</Project>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk">
<PropertyGroup Label="Globals">
<ProjectVersion>2.1</ProjectVersion>
<DockerTargetOS>Linux</DockerTargetOS>
<ProjectGuid>1e938b61-e2a1-4cd5-91dc-f07329cd5daa</ProjectGuid>
<DockerComposeUpArguments>--build<DockerComposeUpArguments/>
<DockerLaunchAction>LaunchBrowser</DockerLaunchAction>
<DockerServiceUrl>{Scheme}://localhost:{ServicePort}/swagger</DockerServiceUrl>
<DockerServiceName>mobileapi</DockerServiceName>
</PropertyGroup>
<ItemGroup>
<None Include="docker-compose.override.yml">
<DependentUpon>docker-compose.yml</DependentUpon>
</None>
<None Include="docker-compose.yml" />
<None Include=".dockerignore" />
</ItemGroup>
<ProjectExtensions>
<VisualStudio>
<UserProperties launchsettings_1json__JsonSchema="" />
</VisualStudio>
</ProjectExtensions>
</Project>

View file

@ -0,0 +1,40 @@
version: '3.4'
services:
mobileapi:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=https://+:443;http://+:80
ports:
- "80"
- "443"
volumes:
- ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
pushapi:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=https://+:443;http://+:80
ports:
- "80"
- "443"
volumes:
- ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
pushsender:
environment:
- DOTNET_ENVIRONMENT=Development
volumes:
- ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
statsapi:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=https://+:443;http://+:80
ports:
- "80"
- "443"
volumes:
- ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro

View file

@ -0,0 +1,82 @@
version: '3.4'
services:
postgres:
container_name: postgres-mobile
image: postgres
environment:
POSTGRES_PASSWORD: postgres
volumes:
- ./postgres-data:/var/lib/postgresql/data
rabbitmq:
container_name: rabbitmq-push
image: rabbitmq
ports:
- 15672:15672
mobileapi:
container_name: mobile-api
image: ${DOCKER_REGISTRY-}mobileapi
build:
context: .
dockerfile: MobileAPI/Dockerfile
environment:
- ASPNETCORE_URLS=https://+:443;http://+:80
- ASPNETCORE_HTTPS_PORT=443
depends_on:
- postgres
ports:
- 8701:80
- 8702:443
pushapi:
container_name: push-api
image: ${DOCKER_REGISTRY-}pushapi
build:
context: .
dockerfile: PushAPI/Dockerfile
environment:
- ASPNETCORE_URLS=https://+:443;http://+:80
- ASPNETCORE_HTTPS_PORT=443
depends_on:
- postgres
- rabbitmq
ports:
- 8703:80
- 8704:443
pushsender:
container_name: push-sender
image: ${DOCKER_REGISTRY-}pushsender
build:
context: .
dockerfile: PushSender/Dockerfile
depends_on:
- rabbitmq
statswebsite:
container_name: stats-website
image: ${DOCKER_REGISTRY-}statswebsite
build:
context: stats-website
dockerfile: Dockerfile
depends_on:
- statsapi
ports:
- 3000:3000
statsapi:
container_name: stats-api
image: ${DOCKER_REGISTRY-}statsapi
build:
context: .
dockerfile: StatsAPI/Dockerfile
environment:
- ASPNETCORE_URLS=https://+:443;http://+:80
- ASPNETCORE_HTTPS_PORT=443
depends_on:
- postgres
ports:
- 8705:80
- 8706:443

View file

@ -0,0 +1,18 @@
{
"profiles": {
"Docker Compose": {
"commandName": "DockerCompose",
"commandVersion": "1.0",
"composeLaunchAction": "LaunchBrowser",
"composeLaunchServiceName": "statswebsite",
"composeLaunchUrl": "{Scheme}://localhost:{ServicePort}",
"serviceActions": {
"mobileapi": "StartDebugging",
"postgres": "StartWithoutDebugging",
"pushapi": "StartDebugging",
"pushsender": "StartDebugging",
"rabbitmq": "StartWithoutDebugging"
}
}
}
}

View file

@ -0,0 +1 @@
14

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

Binary file not shown.

View file

View file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

Binary file not shown.

View file

View file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

Some files were not shown because too many files have changed in this diff Show more