diff --git a/Expedience.Api/Consumers/DutyCompletionResultConsumer.cs b/Expedience.Api/Consumers/DutyCompletionResultConsumer.cs new file mode 100644 index 0000000..8a8ecae --- /dev/null +++ b/Expedience.Api/Consumers/DutyCompletionResultConsumer.cs @@ -0,0 +1,101 @@ +using System; +using Expedience.Api.Db; +using Expedience.Api.Db.Models; +using MassTransit; + +namespace Expedience.Api.Consumers +{ + public class DutyCompletionResultConsumer : IConsumer + { + private readonly IServiceScopeFactory _serviceScopeFactory; + + public DutyCompletionResultConsumer(IServiceScopeFactory serviceScopeFactory) + { + _serviceScopeFactory = serviceScopeFactory; + } + + public async Task Consume(ConsumeContext context) + { + var message = context.Message; + + using var scope = _serviceScopeFactory.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + if (message == null || message.UserInfo == null || message.PlayerInfo == null || + message.DutyInfo == null || message.ClientInfo == null || message.CompletionTimeInfo == null) + { + // TODO: Log error + return; + } + + var userHash = message.UserInfo.UserId; + var worldId = message.UserInfo.WorldId; + + var user = dbContext.Users.FirstOrDefault(x => x.UserHash == userHash && x.WorldId == worldId); + if (user == null) + { + string userName; + do + { + userName = UserNameGenerator.Generate(); + } + while (dbContext.Users.Any(x => x.UserName == userName && x.WorldId == worldId)); + + user = new User + { + UserHash = userHash, + WorldId = worldId, + UserName = userName, + CreatedAt = DateTime.UtcNow, + }; + + dbContext.Users.Add(user); + dbContext.SaveChanges(); + } + + var completionResult = new DutyCompletionResult + { + Id = message.UploadId, + DutyId = message.DutyInfo.DutyId, + UserId = user.UserId, + HasEcho = message.DutyInfo.HasEcho, + IsUnrestricted = message.DutyInfo.IsUnrestricted, + HasNpcMembers = message.DutyInfo.IsNpcSupported, + StartTime = message.DutyStartDateUtc, + EndTime = message.DutyCompletionDateUtc, + Hours = message.CompletionTimeInfo.Hours, + Minutes = message.CompletionTimeInfo.Minutes, + Seconds = message.CompletionTimeInfo.Seconds, + Milliseconds = message.CompletionTimeInfo.Milliseconds, + GameVersion = message.ClientInfo.GameVersion, + PluginVersion = message.ClientInfo.PluginVersion, + Language = message.ClientInfo.GameLanguage, + DataCenter = message.DataCenter, + }; + + dbContext.DutyCompletionResults.Add(completionResult); + + var memberNumber = 0; + var dutyMembers = new List(); + foreach (var member in message.GroupMembers) + { + var dutyMember = new DutyMember + { + UploadId = message.UploadId, + GroupNumber = member.GroupNumber, + MemberNumber = memberNumber++, + ClassJob = member.ClassJob, + Level = member.Level, + IsNpc = member.IsNpc, + IsPlayer = member.IsPlayer, + }; + + dutyMembers.Add(dutyMember); + } + + dbContext.DutyMembers.AddRange(dutyMembers); + + await dbContext.SaveChangesAsync(); + } + } +} diff --git a/Expedience.Api/Controllers/ExpedienceController.cs b/Expedience.Api/Controllers/ExpedienceController.cs new file mode 100644 index 0000000..eccfab2 --- /dev/null +++ b/Expedience.Api/Controllers/ExpedienceController.cs @@ -0,0 +1,46 @@ +using Expedience.Api.Encryption; +using Expedience.Models; +using MassTransit; +using Microsoft.AspNetCore.Mvc; +using System.Text.Json; + +namespace Expedience.Api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ExpedienceController : ControllerBase + { + private readonly ILogger _logger; + private readonly IPublishEndpoint _publisher; + private readonly IDecryptor _decrypytor; + + public ExpedienceController(ILogger logger, IPublishEndpoint publisher, IDecryptor decrypytor) + { + _logger = logger; + _publisher = publisher; + _decrypytor = decrypytor; + } + + [HttpPost("DutyCompletion")] + public async Task PostDutyCompletionResult(List encryptedPayloads) + { + var utcNow = DateTime.UtcNow; + try + { + foreach (var payload in encryptedPayloads) + { + var decryptedData = _decrypytor.Decrypt(payload); + var completionResult = JsonSerializer.Deserialize(decryptedData)!; + completionResult.UploadDateUtc = utcNow; + await _publisher.Publish(completionResult); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error publishing completion result: {errorMessage}", ex.Message); + } + + return Ok(); + } + } +} diff --git a/Expedience.Api/Controllers/WeatherController.cs b/Expedience.Api/Controllers/WeatherController.cs new file mode 100644 index 0000000..cc568d6 --- /dev/null +++ b/Expedience.Api/Controllers/WeatherController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Expedience.Api.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} \ No newline at end of file diff --git a/Expedience.Api/Db/ExpedienceContext.cs b/Expedience.Api/Db/ExpedienceContext.cs new file mode 100644 index 0000000..eb6cbc1 --- /dev/null +++ b/Expedience.Api/Db/ExpedienceContext.cs @@ -0,0 +1,52 @@ +using Expedience.Api.Db.Models; +using Microsoft.EntityFrameworkCore; + +namespace Expedience.Api.Db +{ + public class ExpedienceContext : DbContext + { + public ExpedienceContext(DbContextOptions dbContextOptions) : base(dbContextOptions) + { + } + + public DbSet DutyCompletionResults { get; set; } + + public DbSet DutyMembers { get; set; } + + public DbSet Users { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(cfg => + { + cfg.ToTable(nameof(DutyCompletionResult)); + cfg.HasKey(e => e.Id); + }); + + modelBuilder.Entity(cfg => + { + cfg.ToTable(nameof(DutyMember)); + + cfg.HasKey(e => new { e.UploadId, e.GroupNumber, e.MemberNumber }); + + cfg.HasOne(o => o.DutyCompletionResult) + .WithMany(o => o.DutyMembers) + .HasForeignKey(o => o.UploadId); + }); + + modelBuilder.Entity(cfg => + { + cfg.ToTable(nameof(User)); + + cfg.Property(e => e.UserId) + .UseIdentityAlwaysColumn() + .IsRequired() + .ValueGeneratedOnAdd(); + + cfg.HasKey(e => new { e.UserId }); + }); + + base.OnModelCreating(modelBuilder); + } + } +} diff --git a/Expedience.Api/Db/Models/DutyCompletionResult.cs b/Expedience.Api/Db/Models/DutyCompletionResult.cs new file mode 100644 index 0000000..ca9419e --- /dev/null +++ b/Expedience.Api/Db/Models/DutyCompletionResult.cs @@ -0,0 +1,23 @@ +namespace Expedience.Api.Db.Models +{ + public class DutyCompletionResult + { + public Guid Id { get; set; } + public int UserId { get; set; } + public int DutyId { get; set; } + public bool HasEcho { get; set; } + public bool IsUnrestricted { get; set; } + public bool HasNpcMembers { get; set; } + public DateTime StartTime { get; set; } + public DateTime EndTime { get; set; } + public int Hours { get; set; } + public int Minutes { get; set; } + public int Seconds { get; set; } + public int Milliseconds { get; set; } + public string GameVersion { get; set; } + public string PluginVersion { get; set; } + public string Language { get; set; } + public List DutyMembers { get; set; } + public string DataCenter { get; set; } + } +} diff --git a/Expedience.Api/Db/Models/DutyMember.cs b/Expedience.Api/Db/Models/DutyMember.cs new file mode 100644 index 0000000..2105c2c --- /dev/null +++ b/Expedience.Api/Db/Models/DutyMember.cs @@ -0,0 +1,14 @@ +namespace Expedience.Api.Db.Models +{ + public class DutyMember + { + public Guid UploadId { get; set; } + public int GroupNumber { get; set; } + public int MemberNumber { get; set; } + public string ClassJob { get; set; } + public int Level { get; set; } + public bool IsNpc { get; set; } + public bool IsPlayer { get; set; } + public DutyCompletionResult DutyCompletionResult { get; set; } + } +} diff --git a/Expedience.Api/Db/Models/User.cs b/Expedience.Api/Db/Models/User.cs new file mode 100644 index 0000000..5d8c1da --- /dev/null +++ b/Expedience.Api/Db/Models/User.cs @@ -0,0 +1,11 @@ +namespace Expedience.Api.Db.Models +{ + public class User + { + public int UserId { get; set; } + public int WorldId { get; set; } + public string UserHash { get; set; } + public string UserName { get; set; } + public DateTime CreatedAt { get; set; } + } +} diff --git a/Expedience.Api/Db/UserNameGenerator.cs b/Expedience.Api/Db/UserNameGenerator.cs new file mode 100644 index 0000000..7c0c07f --- /dev/null +++ b/Expedience.Api/Db/UserNameGenerator.cs @@ -0,0 +1,480 @@ +using System; + +namespace Expedience.Api.Db +{ + public static class UserNameGenerator + { + private static readonly Random _random; + static UserNameGenerator() + { + int seed = (int)DateTime.Now.Ticks & 0x0000FFFF; + _random = new Random(seed); + } + + public static string Generate() + { + var firstIndex = _random.Next(_adjectives.Count); + var lastIndex = _random.Next(_names.Count); + var firstName = _adjectives[firstIndex]; + var lastName = _names[lastIndex]; + return $"{firstName} {lastName}"; + } + + public static readonly List _names = new List() + { + "Strife", + "Valentine", + "Highwind", + "Gainsborough", + "Lockhart", + "Wallace", + "Leonhart", + "Zangan", + "Cidolfus", + "Kinneas", + "Trepe", + "Loire", + "Rinoa", + "Almasy", + "Caraway", + "Heartilly", + "Leonis", + "Harlow", + "Nox Fleuret", + "Ulric", + "Amicitia", + "Caelum", + "Scientia", + "Argentum", + "Aurum", + "Lucis", + "Estheim", + "Villiers", + "Farron", + "Katzroy", + "Nabaat", + "Yaag Rosch", + "Oerba", + "Paddra", + "Lesca", + "Folles", + "Zephyr", + "Martine", + "Beoulve", + "Gaffgarion", + "Loffrey", + "Mewt", + "Totema", + "Baert", + "Bervenia", + "Malak", + "Ladd", + "Kadaj", + "Loz", + "Yazoo", + "Jenova", + "Sephiroth", + "Hojo", + "Heidegger", + "Scarlet", + "Reeve", + "Palmer", + "Tseng", + "Reno", + "Rude", + "Elmyra", + "Fair", + "Gast", + "Nanaki", + "Shelke", + "Azul", + "Nero", + "Calca", + "Brina", + "Shirma", + "Hilda", + "Croma", + "Aleria", + "Reinhold", + "Rosenburg", + "Deneb", + "Cassiopeia", + "Vega", + "Altair", + "Antares", + "Andromeda", + "Cygnus", + "Fomalhaut", + "Aldebaran", + "Betelgeuse", + "Bellatrix", + "Regulus", + "Sirius", + "Arcturus", + "Pollux", + "Castor", + "Capella", + "Procyon", + "Achernar", + "Alphard", + "Alcyone", + "Atlas", + "Hyperion", + "Iapetus", + "Tethys", + "Cloud", + "Tifa", + "Aerith", + "Zack", + "Sephiroth", + "Vincent", + "Yuffie", + "Barret", + "Red XIII", + "Cait Sith", + "Cid", + "Squall", + "Rinoa", + "Selphie", + "Quistis", + "Zell", + "Irvine", + "Laguna", + "Kiros", + "Ward", + "Edea", + "Seifer", + "Ultimecia", + "Vivi", + "Garnet", + "Zidane", + "Steiner", + "Freya", + "Amarant", + "Beatrix", + "Eiko", + "Kuja", + "Tidus", + "Yuna", + "Wakka", + "Lulu", + "Kimahri", + "Auron", + "Rikku", + "Paine", + "Vaan", + "Ashe", + "Balthier", + "Fran", + "Basch", + "Lightning", + "Snow", + "Hope", + "Vanille", + "Fang", + "Serah", + "Noel", + "Caius", + "Rydia", + "Rosa", + "Cecil", + "Kain", + "Golbez", + "Edge", + "Porom", + "Palom", + "Terra", + "Locke", + "Celes", + "Edgar", + "Sabin", + "Shadow", + "Cyan", + "Setzer", + "Mog", + "Gau", + "Umaro", + "Relm", + "Strago", + "Gogo", + "Zemus", + "Fusoya", + "Tellah", + "Edward", + "Yang", + "Ursula", + "Firion", + "Maria", + "Guy", + "Leon", + "Minwu", + "Josef", + "Ricard", + "Refia", + "Luneth", + "Arc", + "Ingus", + "Krile", + "Galuf", + "Bartz", + "Lenna", + "Faris", + "Exdeath", + "Gilgamesh", + "Cidolfus" + }; + + public static List _adjectives = new List + { + "Adventurous", + "Affectionate", + "Agile", + "Ambitious", + "Amiable", + "Artistic", + "Assertive", + "Astute", + "Attentive", + "Authentic", + "Awesome", + "Balanced", + "Beautiful", + "Blissful", + "Bold", + "Brave", + "Bright", + "Brilliant", + "Calm", + "Capable", + "Carefree", + "Careful", + "Caring", + "Charismatic", + "Charming", + "Cheerful", + "Clean", + "Clear-minded", + "Clever", + "Colorful", + "Comfortable", + "Committed", + "Compassionate", + "Confident", + "Considerate", + "Consistent", + "Contemplative", + "Cooperative", + "Courageous", + "Courteous", + "Creative", + "Cultured", + "Curious", + "Daring", + "Dazzling", + "Decisive", + "Deep", + "Delightful", + "Dependable", + "Determined", + "Devoted", + "Diligent", + "Dynamic", + "Eager", + "Easygoing", + "Eccentric", + "Educated", + "Efficient", + "Elegant", + "Eloquent", + "Empathetic", + "Endearing", + "Energetic", + "Enthusiastic", + "Excellent", + "Exciting", + "Experienced", + "Expressive", + "Extraordinary", + "Exuberant", + "Fabulous", + "Fair", + "Faithful", + "Fantastic", + "Fearless", + "Fierce", + "Flamboyant", + "Flawless", + "Focused", + "Forgiving", + "Friendly", + "Fun", + "Funny", + "Generous", + "Gentle", + "Genuine", + "Gifted", + "Glorious", + "Graceful", + "Gracious", + "Grateful", + "Great", + "Hardworking", + "Harmonious", + "Healthy", + "Heartfelt", + "Helpful", + "Heroic", + "Honest", + "Honorable", + "Hopeful", + "Humble", + "Humorous", + "Idealistic", + "Imaginative", + "Impartial", + "Independent", + "Innovative", + "Inquisitive", + "Insightful", + "Inspiring", + "Intelligent", + "Intuitive", + "Inventive", + "Joyful", + "Jovial", + "Judicious", + "Just", + "Kind", + "Knowledgeable", + "Laid-back", + "Leisurely", + "Liberated", + "Lively", + "Logical", + "Lovable", + "Loving", + "Loyal", + "Lucid", + "Magical", + "Magnificent", + "Majestic", + "Masterful", + "Mature", + "Mellow", + "Meticulous", + "Mindful", + "Modest", + "Motivated", + "Mysterious", + "Nimble", + "Noble", + "Open-minded", + "Optimistic", + "Organized", + "Original", + "Outgoing", + "Passionate", + "Patient", + "Peaceful", + "Perfect", + "Persevering", + "Persistent", + "Playful", + "Pleasant", + "Polite", + "Positive", + "Powerful", + "Precise", + "Proactive", + "Progressive", + "Prosperous", + "Proud", + "Qualified", + "Quick", + "Quiet", + "Radiant", + "Rational", + "Realistic", + "Reflective", + "Relaxed", + "Reliable", + "Resilient", + "Resourceful", + "Respectful", + "Responsible", + "Robust", + "Romantic", + "Sane", + "Satisfying", + "Sensitive", + "Serene", + "Sharp", + "Shrewd", + "Sincere", + "Skillful", + "Smart", + "Smooth", + "Social", + "Solid", + "Sophisticated", + "Soulful", + "Sparkling", + "Special", + "Spiritual", + "Spontaneous", + "Stable", + "Steadfast", + "Steady", + "Strong", + "Stunning", + "Successful", + "Superb", + "Supportive", + "Surprising", + "Sweet", + "Sympathetic", + "Systematic", + "Tactful", + "Talented", + "Tenacious", + "Thankful", + "Thorough", + "Thoughtful", + "Thrilled", + "Thriving", + "Tidy", + "Timely", + "Tolerant", + "Tranquil", + "Transparent", + "Trustworthy", + "Unassuming", + "Understanding", + "Unique", + "Unselfish", + "Unwavering", + "Upbeat", + "Valiant", + "Versatile", + "Vibrant", + "Victorious", + "Vigilant", + "Vigorous", + "Virtuous", + "Visionary", + "Vital", + "Vivacious", + "Warm", + "Welcoming", + "Wholehearted", + "Willing", + "Wise", + "Witty", + "Wonderful", + "Worthy", + "Youthful", + "Zealous", + "Zest" + }; + } +} diff --git a/Expedience.Api/Dockerfile b/Expedience.Api/Dockerfile new file mode 100644 index 0000000..3d9bf5b --- /dev/null +++ b/Expedience.Api/Dockerfile @@ -0,0 +1,39 @@ +# Set the base image +FROM mcr.microsoft.com/dotnet/sdk:7.0.202-jammy-arm64v8 AS build + +# Set the working directory +WORKDIR /src + +# Copy the project files +COPY Expedience.Models/Expedience.Models.csproj Expedience.Models/ +COPY Expedience.Api/Expedience.Api.csproj Expedience.Api/ + +# Restore the packages +RUN dotnet restore Expedience.Api/Expedience.Api.csproj + +COPY . . + +# Build the project +RUN dotnet build "Expedience.Api/Expedience.Api.csproj" -c Release -o /app/build + +# Publish the project +ARG TARGET_RUNTIME=linux-x64 +RUN dotnet publish "Expedience.Api/Expedience.Api.csproj" -c Release -o /app/publish --self-contained --runtime $TARGET_RUNTIME + +# Set the base image for the final runtime +FROM mcr.microsoft.com/dotnet/aspnet:7.0.4-jammy-arm64v8 + +# Set the working directory +WORKDIR /app + +# Copy the published files +COPY --from=build /app/publish . + +# Set the ASPNETCORE_ENVIRONMENT environment variable +ENV ASPNETCORE_ENVIRONMENT=Production + +# Expose the port the application will run on +EXPOSE 7033 + +# Set the entry point for the container +ENTRYPOINT ["dotnet", "Expedience.Api.dll"] \ No newline at end of file diff --git a/Expedience.Api/Encryption/Decryptor.cs b/Expedience.Api/Encryption/Decryptor.cs new file mode 100644 index 0000000..e8a09c3 --- /dev/null +++ b/Expedience.Api/Encryption/Decryptor.cs @@ -0,0 +1,51 @@ +using RabbitMQ.Client; +using System.Reflection; +using System.Security.Cryptography; +using System.Security.Cryptography.Xml; +using System.Text; + +namespace Expedience.Api.Encryption +{ + public interface IDecryptor + { + string Decrypt(string data); + } + + public class Decryptor : IDecryptor + { + private RSACryptoServiceProvider _csp; + + public Decryptor() + { + _csp = new RSACryptoServiceProvider(); + using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Expedience.Api.key.bin") + ?? throw new NullReferenceException("Could not get key.bin resource"); + + using var memorySteam = new MemoryStream(); + stream.CopyTo(memorySteam); + var bytes = memorySteam.ToArray(); + _csp.ImportCspBlob(bytes); + + } + + public string Decrypt(string data) + { + var encryptedData = Convert.FromBase64String(data); + + // Split the encrypted data into chunks + byte[] decryptedData = Array.Empty(); + for (int i = 0; i < encryptedData.Length; i += _csp.KeySize / 8) + { + int dataSize = _csp.KeySize / 8; + byte[] chunk = new byte[dataSize]; + Array.Copy(encryptedData, i, chunk, 0, dataSize); + + // Decrypt the chunk of data + byte[] decryptedChunk = _csp.Decrypt(chunk, false); + decryptedData = decryptedData.Concat(decryptedChunk).ToArray(); + } + + return Encoding.UTF8.GetString(decryptedData); + } + } +} diff --git a/Expedience.Api/Expedience.Api.csproj b/Expedience.Api/Expedience.Api.csproj new file mode 100644 index 0000000..22d9d8d --- /dev/null +++ b/Expedience.Api/Expedience.Api.csproj @@ -0,0 +1,36 @@ + + + + net7.0 + enable + enable + Linux + ..\Expedience + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Expedience.Api/Program.cs b/Expedience.Api/Program.cs new file mode 100644 index 0000000..405444a --- /dev/null +++ b/Expedience.Api/Program.cs @@ -0,0 +1,54 @@ +using Expedience.Api.Consumers; +using Expedience.Api.Db; +using Expedience.Api.Encryption; +using MassTransit; +using Microsoft.EntityFrameworkCore; +using System.Reflection; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +var connectionString = builder.Configuration.GetConnectionString("Expedience"); +builder.Services.AddDbContext(options => + options.UseNpgsql(connectionString)); + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var rabbitMqHost = builder.Configuration.GetSection("RabbitMq")["Host"]; +builder.Services.AddMassTransit(opt => +{ + opt.AddConsumersFromNamespaceContaining(); + + opt.UsingRabbitMq((context, cfg) => + { + cfg.Host(rabbitMqHost); + cfg.ConfigureEndpoints(context); + }); +}); + +builder.Services.AddSingleton(); + +var app = builder.Build(); + +var scope = app.Services.CreateScope(); +var dbContext = scope.ServiceProvider.GetRequiredService(); + +dbContext.Database.EnsureCreated(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/Expedience.Api/Properties/launchSettings.json b/Expedience.Api/Properties/launchSettings.json new file mode 100644 index 0000000..1799d37 --- /dev/null +++ b/Expedience.Api/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:16060", + "sslPort": 44320 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5168", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7033;http://localhost:5168", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Expedience.Api/WeatherForecast.cs b/Expedience.Api/WeatherForecast.cs new file mode 100644 index 0000000..3d760e2 --- /dev/null +++ b/Expedience.Api/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace Expedience.Api +{ + public class WeatherForecast + { + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} \ No newline at end of file diff --git a/Expedience.Api/appsettings.json b/Expedience.Api/appsettings.json new file mode 100644 index 0000000..23160a4 --- /dev/null +++ b/Expedience.Api/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Expedience.Api/deployment.yaml b/Expedience.Api/deployment.yaml new file mode 100644 index 0000000..005af60 --- /dev/null +++ b/Expedience.Api/deployment.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: expedience-api-deployment + namespace: expedience +spec: + replicas: 1 + selector: + matchLabels: + app: expedience-api + template: + metadata: + labels: + app: expedience-api + spec: + containers: + - name: expedience-api + image: registry.ilitirit.net/expedience-api:latest + ports: + - containerPort: 7033 + imagePullSecrets: + - name: registry-credentials \ No newline at end of file diff --git a/Expedience.Api/ingress.yaml b/Expedience.Api/ingress.yaml new file mode 100644 index 0000000..6f6cb1c --- /dev/null +++ b/Expedience.Api/ingress.yaml @@ -0,0 +1,23 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: expedience-api-ingress + namespace: expedience + annotations: + kubernetes.io/ingress.class: "nginx" + cert-manager.io/cluster-issuer: letsencrypt-prod +spec: + tls: + - hosts: + - expedience-api.ilitirit.net + secretName: expedience-api-certificate + rules: + - host: expedience-api.ilitirit.net + http: + paths: + - pathType: ImplementationSpecific + backend: + service: + name: expedience-api-service + port: + number: 7033 diff --git a/Expedience.Api/service.yaml b/Expedience.Api/service.yaml new file mode 100644 index 0000000..a9294ef --- /dev/null +++ b/Expedience.Api/service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: expedience-api-service + namespace: expedience +spec: + selector: + app: expedience-api + ports: + - protocol: TCP + port: 7033 + targetPort: 7033 + type: ClusterIP