Updated translation service and finished splitting out responsibilities for now

This commit is contained in:
gamer147
2025-11-18 10:07:23 -05:00
parent 3bb8f7f158
commit 0c1705ebe1
48 changed files with 617 additions and 1134 deletions

View File

@@ -29,6 +29,9 @@
<Content Include="..\.dockerignore">
<Link>.dockerignore</Link>
</Content>
<Content Update="appsettings.Development.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>

View File

@@ -1,14 +1,30 @@
using FictionArchive.Common.Enums;
using FictionArchive.Service.TranslationService.Models.Database;
using FictionArchive.Service.TranslationService.Models.Enums;
using FictionArchive.Service.TranslationService.Services.Database;
using FictionArchive.Service.TranslationService.Services.TranslationEngines;
namespace FictionArchive.Service.TranslationService.GraphQL;
public class Mutation
{
public async Task<string> TranslateText(string text, Language from, Language to, string translationEngineKey, IEnumerable<ITranslationEngineAdapter> translationEngines)
public async Task<string> TranslateText(string text, Language from, Language to, string translationEngineKey, IEnumerable<ITranslationEngine> translationEngines, TranslationServiceDbContext dbContext)
{
var engine = translationEngines.FirstOrDefault(engine => engine.Descriptor.Key == translationEngineKey);
var translation = await engine.GetTranslation(text, from, to);
dbContext.TranslationRequests.Add(new TranslationRequest()
{
OriginalText = text,
BilledCharacterCount = 0, // FILL ME
From = from,
To = to,
Status = translation != null ? TranslationRequestStatus.Success : TranslationRequestStatus.Failed,
TranslatedText = translation,
TranslationEngineKey = translationEngineKey
});
await dbContext.SaveChangesAsync();
return translation;
}
}

View File

@@ -7,7 +7,7 @@ public class Query
{
[UseFiltering]
[UseSorting]
public IEnumerable<TranslationEngineDescriptor> GetTranslationEngines(IEnumerable<ITranslationEngineAdapter> engines)
public IEnumerable<TranslationEngineDescriptor> GetTranslationEngines(IEnumerable<ITranslationEngine> engines)
{
return engines.Select(engine => engine.Descriptor);
}

View File

@@ -0,0 +1,72 @@
// <auto-generated />
using FictionArchive.Service.TranslationService.Services.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using NodaTime;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace FictionArchive.Service.TranslationService.Migrations
{
[DbContext(typeof(TranslationServiceDbContext))]
[Migration("20251118052322_Initial")]
partial class Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.11")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("FictionArchive.Service.TranslationService.Models.Database.TranslationRequest", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<long>("BilledCharacterCount")
.HasColumnType("bigint");
b.Property<Instant>("CreatedTime")
.HasColumnType("timestamp with time zone");
b.Property<int>("From")
.HasColumnType("integer");
b.Property<Instant>("LastUpdatedTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("OriginalText")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<int>("To")
.HasColumnType("integer");
b.Property<string>("TranslatedText")
.HasColumnType("text");
b.Property<string>("TranslationEngineKey")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("TranslationRequests");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,44 @@
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace FictionArchive.Service.TranslationService.Migrations
{
/// <inheritdoc />
public partial class Initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "TranslationRequests",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
OriginalText = table.Column<string>(type: "text", nullable: false),
TranslatedText = table.Column<string>(type: "text", nullable: true),
From = table.Column<int>(type: "integer", nullable: false),
To = table.Column<int>(type: "integer", nullable: false),
TranslationEngineKey = table.Column<string>(type: "text", nullable: false),
Status = table.Column<int>(type: "integer", nullable: false),
BilledCharacterCount = table.Column<long>(type: "bigint", nullable: false),
CreatedTime = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
LastUpdatedTime = table.Column<Instant>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_TranslationRequests", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "TranslationRequests");
}
}
}

View File

@@ -0,0 +1,69 @@
// <auto-generated />
using FictionArchive.Service.TranslationService.Services.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using NodaTime;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace FictionArchive.Service.TranslationService.Migrations
{
[DbContext(typeof(TranslationServiceDbContext))]
partial class TranslationServiceDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.11")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("FictionArchive.Service.TranslationService.Models.Database.TranslationRequest", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<long>("BilledCharacterCount")
.HasColumnType("bigint");
b.Property<Instant>("CreatedTime")
.HasColumnType("timestamp with time zone");
b.Property<int>("From")
.HasColumnType("integer");
b.Property<Instant>("LastUpdatedTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("OriginalText")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<int>("To")
.HasColumnType("integer");
b.Property<string>("TranslatedText")
.HasColumnType("text");
b.Property<string>("TranslationEngineKey")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("TranslationRequests");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,16 @@
using FictionArchive.Common.Enums;
using FictionArchive.Service.Shared.Models;
using FictionArchive.Service.TranslationService.Models.Enums;
namespace FictionArchive.Service.TranslationService.Models.Database;
public class TranslationRequest : BaseEntity<Guid>
{
public required string OriginalText { get; set; }
public string? TranslatedText { get; set; }
public Language From { get; set; }
public Language To { get; set; }
public required string TranslationEngineKey { get; set; }
public TranslationRequestStatus Status { get; set; }
public uint BilledCharacterCount { get; set; }
}

View File

@@ -0,0 +1,8 @@
namespace FictionArchive.Service.TranslationService.Models.Enums;
public enum TranslationRequestStatus
{
Failed = -1,
Pending = 0,
Success = 1,
}

View File

@@ -1,6 +1,9 @@
using DeepL;
using FictionArchive.Common.Extensions;
using FictionArchive.Service.Shared.Extensions;
using FictionArchive.Service.Shared.Services.GraphQL;
using FictionArchive.Service.TranslationService.GraphQL;
using FictionArchive.Service.TranslationService.Services.Database;
using FictionArchive.Service.TranslationService.Services.TranslationEngines;
using FictionArchive.Service.TranslationService.Services.TranslationEngines.DeepLTranslate;
@@ -11,19 +14,19 @@ public class Program
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.AddLocalAppsettings();
builder.Services.AddHealthChecks();
#region Database
builder.Services.RegisterDbContext<TranslationServiceDbContext>(builder.Configuration.GetConnectionString("DefaultConnection"));
#endregion
#region GraphQL
builder.Services.AddGraphQLServer()
.AddQueryType<Query>()
.AddMutationType<Mutation>()
.AddType<UnsignedIntType>()
.AddMutationConventions(applyToAllMutations: true)
.AddFiltering(opt => opt.AddDefaults().BindRuntimeType<uint, UnsignedIntOperationFilterInputType>())
.AddSorting()
.AddProjections();
builder.Services.AddDefaultGraphQl<Query, Mutation>();
#endregion
@@ -33,7 +36,7 @@ public class Program
{
return new DeepLClient(builder.Configuration["DeepL:ApiKey"]);
});
builder.Services.AddTransient<ITranslationEngineAdapter, DeepLTranslationAdapater>();
builder.Services.AddTransient<ITranslationEngine, DeepLTranslationEngine>();
#endregion

View File

@@ -0,0 +1,14 @@
using FictionArchive.Service.Shared.Services.Database;
using FictionArchive.Service.TranslationService.Models.Database;
using Microsoft.EntityFrameworkCore;
namespace FictionArchive.Service.TranslationService.Services.Database;
public class TranslationServiceDbContext : FictionArchiveDbContext
{
public DbSet<TranslationRequest> TranslationRequests { get; set; }
public TranslationServiceDbContext(DbContextOptions options, ILogger<TranslationServiceDbContext> logger) : base(options, logger)
{
}
}

View File

@@ -5,15 +5,15 @@ using Language = FictionArchive.Common.Enums.Language;
namespace FictionArchive.Service.TranslationService.Services.TranslationEngines.DeepLTranslate;
public class DeepLTranslationAdapater : ITranslationEngineAdapter
public class DeepLTranslationEngine : ITranslationEngine
{
private readonly DeepLClient _deepLClient;
private readonly ILogger<DeepLTranslationAdapater> _logger;
private readonly ILogger<DeepLTranslationEngine> _logger;
private const string DisplayName = "DeepL";
private const string Key = "deepl";
public DeepLTranslationAdapater(DeepLClient deepLClient, ILogger<DeepLTranslationAdapater> logger)
public DeepLTranslationEngine(DeepLClient deepLClient, ILogger<DeepLTranslationEngine> logger)
{
_deepLClient = deepLClient;
_logger = logger;
@@ -45,7 +45,8 @@ public class DeepLTranslationAdapater : ITranslationEngineAdapter
Language.En => LanguageCode.EnglishAmerican,
Language.Kr => LanguageCode.Korean,
Language.Ch => LanguageCode.Chinese,
Language.Ja => LanguageCode.Japanese
Language.Ja => LanguageCode.Japanese,
_ => throw new ArgumentOutOfRangeException(nameof(language), language, null)
};
}
}

View File

@@ -3,7 +3,7 @@ using FictionArchive.Service.TranslationService.Models;
namespace FictionArchive.Service.TranslationService.Services.TranslationEngines;
public interface ITranslationEngineAdapter
public interface ITranslationEngine
{
public TranslationEngineDescriptor Descriptor { get; }
public Task<string?> GetTranslation(string body, Language from, Language to);

View File

@@ -8,5 +8,8 @@
"DeepL": {
"ApiKey": "REPLACE_ME"
},
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Database=FictionArchive_NovelService;Username=postgres;password=postgres"
},
"AllowedHosts": "*"
}