[FA-4] Adds an event bus infrastructure, a RabbitMQ implementation and rewires existing mutations on NovelService to utilize it.
This commit is contained in:
@@ -8,6 +8,10 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2"/>
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,107 +1,37 @@
|
|||||||
using FictionArchive.Service.NovelService.Models.Enums;
|
using FictionArchive.Service.NovelService.Models.Enums;
|
||||||
|
using FictionArchive.Service.NovelService.Models.IntegrationEvents;
|
||||||
using FictionArchive.Service.NovelService.Models.Localization;
|
using FictionArchive.Service.NovelService.Models.Localization;
|
||||||
using FictionArchive.Service.NovelService.Models.Novels;
|
using FictionArchive.Service.NovelService.Models.Novels;
|
||||||
using FictionArchive.Service.NovelService.Models.SourceAdapters;
|
using FictionArchive.Service.NovelService.Models.SourceAdapters;
|
||||||
using FictionArchive.Service.NovelService.Services;
|
using FictionArchive.Service.NovelService.Services;
|
||||||
using FictionArchive.Service.NovelService.Services.SourceAdapters;
|
using FictionArchive.Service.NovelService.Services.SourceAdapters;
|
||||||
|
using FictionArchive.Service.Shared.Services.EventBus;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace FictionArchive.Service.NovelService.GraphQL;
|
namespace FictionArchive.Service.NovelService.GraphQL;
|
||||||
|
|
||||||
public class Mutation
|
public class Mutation
|
||||||
{
|
{
|
||||||
// TODO Make this kick off a job in the background somehow. Probably want to think of how jobs will work across services
|
public async Task<NovelUpdateRequestedEvent> ImportNovel(string novelUrl, IEventBus eventBus)
|
||||||
// Also of course need to make it a proper 'upsert'
|
|
||||||
public async Task<Novel> ImportNovel(string novelUrl, NovelServiceDbContext dbContext,
|
|
||||||
IEnumerable<ISourceAdapter> adapters)
|
|
||||||
{
|
{
|
||||||
NovelMetadata? metadata = null;
|
var importNovelRequestEvent = new NovelUpdateRequestedEvent()
|
||||||
foreach (ISourceAdapter sourceAdapter in adapters)
|
|
||||||
{
|
{
|
||||||
if (await sourceAdapter.CanProcessNovel(novelUrl))
|
NovelUrl = novelUrl
|
||||||
{
|
};
|
||||||
metadata = await sourceAdapter.GetMetadata(novelUrl);
|
await eventBus.Publish(importNovelRequestEvent);
|
||||||
}
|
return importNovelRequestEvent;
|
||||||
}
|
|
||||||
|
|
||||||
if (metadata == null)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException("The provided novel url is currently unsupported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var systemTags = metadata.SystemTags.Select(tag => new NovelTag()
|
|
||||||
{
|
|
||||||
Key = tag,
|
|
||||||
DisplayName = LocalizationKey.CreateFromText(tag, metadata.RawLanguage),
|
|
||||||
TagType = TagType.System
|
|
||||||
});
|
|
||||||
var sourceTags = metadata.SourceTags.Select(tag => new NovelTag()
|
|
||||||
{
|
|
||||||
Key = tag,
|
|
||||||
DisplayName = LocalizationKey.CreateFromText(tag, metadata.RawLanguage),
|
|
||||||
TagType = TagType.External
|
|
||||||
});
|
|
||||||
|
|
||||||
var addedNovel = dbContext.Novels.Add(new Novel()
|
|
||||||
{
|
|
||||||
Author = new Person()
|
|
||||||
{
|
|
||||||
Name = LocalizationKey.CreateFromText(metadata.AuthorName, metadata.RawLanguage),
|
|
||||||
ExternalUrl = metadata.AuthorUrl,
|
|
||||||
},
|
|
||||||
RawLanguage = metadata.RawLanguage,
|
|
||||||
Url = metadata.Url,
|
|
||||||
ExternalId = metadata.ExternalId,
|
|
||||||
Chapters = metadata.Chapters.Select(chapter =>
|
|
||||||
{
|
|
||||||
return new Chapter()
|
|
||||||
{
|
|
||||||
Order = chapter.Order,
|
|
||||||
Url = chapter.Url,
|
|
||||||
Revision = chapter.Revision,
|
|
||||||
Name = LocalizationKey.CreateFromText(chapter.Name, metadata.RawLanguage),
|
|
||||||
Body = new LocalizationKey()
|
|
||||||
{
|
|
||||||
Texts = new List<LocalizationText>()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}).ToList(),
|
|
||||||
Description = LocalizationKey.CreateFromText(metadata.Description, metadata.RawLanguage),
|
|
||||||
Name = LocalizationKey.CreateFromText(metadata.Name, metadata.RawLanguage),
|
|
||||||
RawStatus = metadata.RawStatus,
|
|
||||||
Tags = sourceTags.Concat(systemTags).ToList(),
|
|
||||||
Source = new Source()
|
|
||||||
{
|
|
||||||
Name = metadata.SourceDescriptor.Name,
|
|
||||||
Url = metadata.SourceDescriptor.Url,
|
|
||||||
Key = metadata.SourceDescriptor.Key,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await dbContext.SaveChangesAsync();
|
|
||||||
|
|
||||||
return addedNovel.Entity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Chapter> FetchChapterContents(uint novelId,
|
public async Task<ChapterPullRequestedEvent> FetchChapterContents(uint novelId,
|
||||||
uint chapterNumber,
|
uint chapterNumber,
|
||||||
NovelServiceDbContext dbContext,
|
IEventBus eventBus)
|
||||||
IEnumerable<ISourceAdapter> sourceAdapters)
|
|
||||||
{
|
{
|
||||||
var novel = await dbContext.Novels.Where(novel => novel.Id == novelId)
|
var chapterPullEvent = new ChapterPullRequestedEvent()
|
||||||
.Include(novel => novel.Chapters)
|
|
||||||
.ThenInclude(chapter => chapter.Body)
|
|
||||||
.ThenInclude(body => body.Texts)
|
|
||||||
.Include(novel => novel.Source)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
var chapter = novel.Chapters.Where(chapter => chapter.Order == chapterNumber).FirstOrDefault();
|
|
||||||
var adapter = sourceAdapters.FirstOrDefault(adapter => adapter.SourceDescriptor.Key == novel.Source.Key);
|
|
||||||
var rawChapter = await adapter.GetRawChapter(chapter.Url);
|
|
||||||
chapter.Body.Texts.Add(new LocalizationText()
|
|
||||||
{
|
{
|
||||||
Text = rawChapter,
|
NovelId = novelId,
|
||||||
Language = novel.RawLanguage
|
ChapterNumber = chapterNumber
|
||||||
});
|
};
|
||||||
await dbContext.SaveChangesAsync();
|
await eventBus.Publish(chapterPullEvent);
|
||||||
return chapter;
|
return chapterPullEvent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
using FictionArchive.Service.NovelService.Services;
|
using FictionArchive.Service.NovelService.Services;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
@@ -12,7 +13,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|||||||
namespace FictionArchive.Service.NovelService.Migrations
|
namespace FictionArchive.Service.NovelService.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(NovelServiceDbContext))]
|
[DbContext(typeof(NovelServiceDbContext))]
|
||||||
[Migration("20251118045235_Initial")]
|
[Migration("20251120012317_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -27,11 +28,9 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", b =>
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
|
||||||
|
|
||||||
b.Property<Instant>("CreatedTime")
|
b.Property<Instant>("CreatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
@@ -41,16 +40,44 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("LocalizationKey");
|
b.ToTable("LocalizationKeys");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationRequest", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedTime")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("EngineId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<Guid>("KeyRequestedForTranslationId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Instant>("LastUpdatedTime")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int>("TranslateTo")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("EngineId");
|
||||||
|
|
||||||
|
b.HasIndex("KeyRequestedForTranslationId");
|
||||||
|
|
||||||
|
b.ToTable("LocalizationRequests");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationText", b =>
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationText", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
|
||||||
|
|
||||||
b.Property<Instant>("CreatedTime")
|
b.Property<Instant>("CreatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
@@ -61,8 +88,8 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
b.Property<Instant>("LastUpdatedTime")
|
b.Property<Instant>("LastUpdatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<long?>("LocalizationKeyId")
|
b.Property<Guid?>("LocalizationKeyId")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<string>("Text")
|
b.Property<string>("Text")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -88,8 +115,8 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
b.Property<long>("BodyId")
|
b.Property<Guid>("BodyId")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<Instant>("CreatedTime")
|
b.Property<Instant>("CreatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
@@ -97,8 +124,8 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
b.Property<Instant>("LastUpdatedTime")
|
b.Property<Instant>("LastUpdatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<long>("NameId")
|
b.Property<Guid>("NameId")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<long?>("NovelId")
|
b.Property<long?>("NovelId")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("bigint");
|
||||||
@@ -137,8 +164,8 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
b.Property<Instant>("CreatedTime")
|
b.Property<Instant>("CreatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<long>("DescriptionId")
|
b.Property<Guid>("DescriptionId")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<string>("ExternalId")
|
b.Property<string>("ExternalId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -147,8 +174,8 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
b.Property<Instant>("LastUpdatedTime")
|
b.Property<Instant>("LastUpdatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<long>("NameId")
|
b.Property<Guid>("NameId")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<int>("RawLanguage")
|
b.Property<int>("RawLanguage")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -190,8 +217,8 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
b.Property<Instant>("CreatedTime")
|
b.Property<Instant>("CreatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<long>("DisplayNameId")
|
b.Property<Guid>("DisplayNameId")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<string>("Key")
|
b.Property<string>("Key")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -232,12 +259,13 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
b.Property<Instant>("LastUpdatedTime")
|
b.Property<Instant>("LastUpdatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<Guid>("NameId")
|
||||||
.IsRequired()
|
.HasColumnType("uuid");
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NameId");
|
||||||
|
|
||||||
b.ToTable("Person");
|
b.ToTable("Person");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -283,10 +311,6 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
b.Property<Instant>("CreatedTime")
|
b.Property<Instant>("CreatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<string>("DisplayName")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Key")
|
b.Property<string>("Key")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
@@ -314,6 +338,25 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
b.ToTable("NovelNovelTag");
|
b.ToTable("NovelNovelTag");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationRequest", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.TranslationEngine", "Engine")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("EngineId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "KeyRequestedForTranslation")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("KeyRequestedForTranslationId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Engine");
|
||||||
|
|
||||||
|
b.Navigation("KeyRequestedForTranslation");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationText", b =>
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationText", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", null)
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", null)
|
||||||
@@ -402,6 +445,17 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
b.Navigation("Source");
|
b.Navigation("Source");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Person", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "Name")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("NameId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Name");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelNovelTag", b =>
|
modelBuilder.Entity("NovelNovelTag", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Novel", null)
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Novel", null)
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
@@ -13,33 +14,16 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "LocalizationKey",
|
name: "LocalizationKeys",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
|
||||||
CreatedTime = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
CreatedTime = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
LastUpdatedTime = table.Column<Instant>(type: "timestamp with time zone", nullable: false)
|
LastUpdatedTime = table.Column<Instant>(type: "timestamp with time zone", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_LocalizationKey", x => x.Id);
|
table.PrimaryKey("PK_LocalizationKeys", x => x.Id);
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "Person",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
|
||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
|
||||||
Name = table.Column<string>(type: "text", nullable: false),
|
|
||||||
ExternalUrl = table.Column<string>(type: "text", nullable: true),
|
|
||||||
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_Person", x => x.Id);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
@@ -66,7 +50,6 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
Key = table.Column<string>(type: "text", nullable: false),
|
Key = table.Column<string>(type: "text", nullable: false),
|
||||||
DisplayName = table.Column<string>(type: "text", nullable: false),
|
|
||||||
CreatedTime = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
CreatedTime = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
LastUpdatedTime = table.Column<Instant>(type: "timestamp with time zone", nullable: false)
|
LastUpdatedTime = table.Column<Instant>(type: "timestamp with time zone", nullable: false)
|
||||||
},
|
},
|
||||||
@@ -75,6 +58,112 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
table.PrimaryKey("PK_TranslationEngines", x => x.Id);
|
table.PrimaryKey("PK_TranslationEngines", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Person",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
NameId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
ExternalUrl = table.Column<string>(type: "text", nullable: true),
|
||||||
|
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_Person", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Person_LocalizationKeys_NameId",
|
||||||
|
column: x => x.NameId,
|
||||||
|
principalTable: "LocalizationKeys",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Tags",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
Key = table.Column<string>(type: "text", nullable: false),
|
||||||
|
DisplayNameId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
TagType = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
SourceId = table.Column<long>(type: "bigint", nullable: true),
|
||||||
|
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_Tags", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Tags_LocalizationKeys_DisplayNameId",
|
||||||
|
column: x => x.DisplayNameId,
|
||||||
|
principalTable: "LocalizationKeys",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Tags_Sources_SourceId",
|
||||||
|
column: x => x.SourceId,
|
||||||
|
principalTable: "Sources",
|
||||||
|
principalColumn: "Id");
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "LocalizationRequests",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
KeyRequestedForTranslationId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
TranslateTo = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
EngineId = 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_LocalizationRequests", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_LocalizationRequests_LocalizationKeys_KeyRequestedForTransl~",
|
||||||
|
column: x => x.KeyRequestedForTranslationId,
|
||||||
|
principalTable: "LocalizationKeys",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_LocalizationRequests_TranslationEngines_EngineId",
|
||||||
|
column: x => x.EngineId,
|
||||||
|
principalTable: "TranslationEngines",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "LocalizationText",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Language = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Text = table.Column<string>(type: "text", nullable: false),
|
||||||
|
TranslationEngineId = table.Column<long>(type: "bigint", nullable: true),
|
||||||
|
LocalizationKeyId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
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_LocalizationText", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_LocalizationText_LocalizationKeys_LocalizationKeyId",
|
||||||
|
column: x => x.LocalizationKeyId,
|
||||||
|
principalTable: "LocalizationKeys",
|
||||||
|
principalColumn: "Id");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_LocalizationText_TranslationEngines_TranslationEngineId",
|
||||||
|
column: x => x.TranslationEngineId,
|
||||||
|
principalTable: "TranslationEngines",
|
||||||
|
principalColumn: "Id");
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Novels",
|
name: "Novels",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
@@ -88,8 +177,8 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
StatusOverride = table.Column<int>(type: "integer", nullable: true),
|
StatusOverride = table.Column<int>(type: "integer", nullable: true),
|
||||||
SourceId = table.Column<long>(type: "bigint", nullable: false),
|
SourceId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
ExternalId = table.Column<string>(type: "text", nullable: false),
|
ExternalId = table.Column<string>(type: "text", nullable: false),
|
||||||
NameId = table.Column<long>(type: "bigint", nullable: false),
|
NameId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
DescriptionId = table.Column<long>(type: "bigint", nullable: false),
|
DescriptionId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
CreatedTime = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
CreatedTime = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
LastUpdatedTime = table.Column<Instant>(type: "timestamp with time zone", nullable: false)
|
LastUpdatedTime = table.Column<Instant>(type: "timestamp with time zone", nullable: false)
|
||||||
},
|
},
|
||||||
@@ -97,15 +186,15 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Novels", x => x.Id);
|
table.PrimaryKey("PK_Novels", x => x.Id);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Novels_LocalizationKey_DescriptionId",
|
name: "FK_Novels_LocalizationKeys_DescriptionId",
|
||||||
column: x => x.DescriptionId,
|
column: x => x.DescriptionId,
|
||||||
principalTable: "LocalizationKey",
|
principalTable: "LocalizationKeys",
|
||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Novels_LocalizationKey_NameId",
|
name: "FK_Novels_LocalizationKeys_NameId",
|
||||||
column: x => x.NameId,
|
column: x => x.NameId,
|
||||||
principalTable: "LocalizationKey",
|
principalTable: "LocalizationKeys",
|
||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
@@ -122,63 +211,6 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "Tags",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
|
||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
|
||||||
Key = table.Column<string>(type: "text", nullable: false),
|
|
||||||
DisplayNameId = table.Column<long>(type: "bigint", nullable: false),
|
|
||||||
TagType = table.Column<int>(type: "integer", nullable: false),
|
|
||||||
SourceId = table.Column<long>(type: "bigint", nullable: true),
|
|
||||||
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_Tags", x => x.Id);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_Tags_LocalizationKey_DisplayNameId",
|
|
||||||
column: x => x.DisplayNameId,
|
|
||||||
principalTable: "LocalizationKey",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_Tags_Sources_SourceId",
|
|
||||||
column: x => x.SourceId,
|
|
||||||
principalTable: "Sources",
|
|
||||||
principalColumn: "Id");
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "LocalizationText",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
|
||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
|
||||||
Language = table.Column<int>(type: "integer", nullable: false),
|
|
||||||
Text = table.Column<string>(type: "text", nullable: false),
|
|
||||||
TranslationEngineId = table.Column<long>(type: "bigint", nullable: true),
|
|
||||||
LocalizationKeyId = table.Column<long>(type: "bigint", nullable: true),
|
|
||||||
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_LocalizationText", x => x.Id);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_LocalizationText_LocalizationKey_LocalizationKeyId",
|
|
||||||
column: x => x.LocalizationKeyId,
|
|
||||||
principalTable: "LocalizationKey",
|
|
||||||
principalColumn: "Id");
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_LocalizationText_TranslationEngines_TranslationEngineId",
|
|
||||||
column: x => x.TranslationEngineId,
|
|
||||||
principalTable: "TranslationEngines",
|
|
||||||
principalColumn: "Id");
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Chapter",
|
name: "Chapter",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
@@ -188,8 +220,8 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
Revision = table.Column<long>(type: "bigint", nullable: false),
|
Revision = table.Column<long>(type: "bigint", nullable: false),
|
||||||
Order = table.Column<long>(type: "bigint", nullable: false),
|
Order = table.Column<long>(type: "bigint", nullable: false),
|
||||||
Url = table.Column<string>(type: "text", nullable: true),
|
Url = table.Column<string>(type: "text", nullable: true),
|
||||||
NameId = table.Column<long>(type: "bigint", nullable: false),
|
NameId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
BodyId = table.Column<long>(type: "bigint", nullable: false),
|
BodyId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
NovelId = table.Column<long>(type: "bigint", nullable: true),
|
NovelId = table.Column<long>(type: "bigint", nullable: true),
|
||||||
CreatedTime = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
CreatedTime = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
LastUpdatedTime = table.Column<Instant>(type: "timestamp with time zone", nullable: false)
|
LastUpdatedTime = table.Column<Instant>(type: "timestamp with time zone", nullable: false)
|
||||||
@@ -198,15 +230,15 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Chapter", x => x.Id);
|
table.PrimaryKey("PK_Chapter", x => x.Id);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Chapter_LocalizationKey_BodyId",
|
name: "FK_Chapter_LocalizationKeys_BodyId",
|
||||||
column: x => x.BodyId,
|
column: x => x.BodyId,
|
||||||
principalTable: "LocalizationKey",
|
principalTable: "LocalizationKeys",
|
||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Chapter_LocalizationKey_NameId",
|
name: "FK_Chapter_LocalizationKeys_NameId",
|
||||||
column: x => x.NameId,
|
column: x => x.NameId,
|
||||||
principalTable: "LocalizationKey",
|
principalTable: "LocalizationKeys",
|
||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
@@ -255,6 +287,16 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
table: "Chapter",
|
table: "Chapter",
|
||||||
column: "NovelId");
|
column: "NovelId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_LocalizationRequests_EngineId",
|
||||||
|
table: "LocalizationRequests",
|
||||||
|
column: "EngineId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_LocalizationRequests_KeyRequestedForTranslationId",
|
||||||
|
table: "LocalizationRequests",
|
||||||
|
column: "KeyRequestedForTranslationId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_LocalizationText_LocalizationKeyId",
|
name: "IX_LocalizationText_LocalizationKeyId",
|
||||||
table: "LocalizationText",
|
table: "LocalizationText",
|
||||||
@@ -290,6 +332,11 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
table: "Novels",
|
table: "Novels",
|
||||||
column: "SourceId");
|
column: "SourceId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Person_NameId",
|
||||||
|
table: "Person",
|
||||||
|
column: "NameId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Tags_DisplayNameId",
|
name: "IX_Tags_DisplayNameId",
|
||||||
table: "Tags",
|
table: "Tags",
|
||||||
@@ -307,6 +354,9 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Chapter");
|
name: "Chapter");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "LocalizationRequests");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "LocalizationText");
|
name: "LocalizationText");
|
||||||
|
|
||||||
@@ -326,10 +376,10 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
name: "Person");
|
name: "Person");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "LocalizationKey");
|
name: "Sources");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sources");
|
name: "LocalizationKeys");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
using FictionArchive.Service.NovelService.Services;
|
using FictionArchive.Service.NovelService.Services;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
@@ -24,11 +25,9 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", b =>
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
|
||||||
|
|
||||||
b.Property<Instant>("CreatedTime")
|
b.Property<Instant>("CreatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
@@ -38,16 +37,44 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("LocalizationKey");
|
b.ToTable("LocalizationKeys");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationRequest", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedTime")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("EngineId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<Guid>("KeyRequestedForTranslationId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Instant>("LastUpdatedTime")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int>("TranslateTo")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("EngineId");
|
||||||
|
|
||||||
|
b.HasIndex("KeyRequestedForTranslationId");
|
||||||
|
|
||||||
|
b.ToTable("LocalizationRequests");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationText", b =>
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationText", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
|
||||||
|
|
||||||
b.Property<Instant>("CreatedTime")
|
b.Property<Instant>("CreatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
@@ -58,8 +85,8 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
b.Property<Instant>("LastUpdatedTime")
|
b.Property<Instant>("LastUpdatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<long?>("LocalizationKeyId")
|
b.Property<Guid?>("LocalizationKeyId")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<string>("Text")
|
b.Property<string>("Text")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -85,8 +112,8 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
b.Property<long>("BodyId")
|
b.Property<Guid>("BodyId")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<Instant>("CreatedTime")
|
b.Property<Instant>("CreatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
@@ -94,8 +121,8 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
b.Property<Instant>("LastUpdatedTime")
|
b.Property<Instant>("LastUpdatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<long>("NameId")
|
b.Property<Guid>("NameId")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<long?>("NovelId")
|
b.Property<long?>("NovelId")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("bigint");
|
||||||
@@ -134,8 +161,8 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
b.Property<Instant>("CreatedTime")
|
b.Property<Instant>("CreatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<long>("DescriptionId")
|
b.Property<Guid>("DescriptionId")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<string>("ExternalId")
|
b.Property<string>("ExternalId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -144,8 +171,8 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
b.Property<Instant>("LastUpdatedTime")
|
b.Property<Instant>("LastUpdatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<long>("NameId")
|
b.Property<Guid>("NameId")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<int>("RawLanguage")
|
b.Property<int>("RawLanguage")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -187,8 +214,8 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
b.Property<Instant>("CreatedTime")
|
b.Property<Instant>("CreatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<long>("DisplayNameId")
|
b.Property<Guid>("DisplayNameId")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<string>("Key")
|
b.Property<string>("Key")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -229,12 +256,13 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
b.Property<Instant>("LastUpdatedTime")
|
b.Property<Instant>("LastUpdatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<Guid>("NameId")
|
||||||
.IsRequired()
|
.HasColumnType("uuid");
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NameId");
|
||||||
|
|
||||||
b.ToTable("Person");
|
b.ToTable("Person");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -280,10 +308,6 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
b.Property<Instant>("CreatedTime")
|
b.Property<Instant>("CreatedTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<string>("DisplayName")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("Key")
|
b.Property<string>("Key")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
@@ -311,6 +335,25 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
b.ToTable("NovelNovelTag");
|
b.ToTable("NovelNovelTag");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationRequest", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.TranslationEngine", "Engine")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("EngineId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "KeyRequestedForTranslation")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("KeyRequestedForTranslationId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Engine");
|
||||||
|
|
||||||
|
b.Navigation("KeyRequestedForTranslation");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationText", b =>
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationText", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", null)
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", null)
|
||||||
@@ -399,6 +442,17 @@ namespace FictionArchive.Service.NovelService.Migrations
|
|||||||
b.Navigation("Source");
|
b.Navigation("Source");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Person", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "Name")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("NameId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Name");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NovelNovelTag", b =>
|
modelBuilder.Entity("NovelNovelTag", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Novel", null)
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Novel", null)
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using FictionArchive.Service.Shared.Services.EventBus;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Models.IntegrationEvents;
|
||||||
|
|
||||||
|
public class ChapterPullRequestedEvent : IntegrationEvent
|
||||||
|
{
|
||||||
|
public uint NovelId { get; set; }
|
||||||
|
public uint ChapterNumber { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using FictionArchive.Service.Shared.Services.EventBus;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Models.IntegrationEvents;
|
||||||
|
|
||||||
|
public class NovelUpdateRequestedEvent : IntegrationEvent
|
||||||
|
{
|
||||||
|
public string NovelUrl { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using FictionArchive.Common.Enums;
|
||||||
|
using FictionArchive.Service.Shared.Services.EventBus;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Models.IntegrationEvents;
|
||||||
|
|
||||||
|
public class TranslationRequestCompletedEvent : IntegrationEvent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Maps this event back to a triggering request.
|
||||||
|
/// </summary>
|
||||||
|
public Guid? TranslationRequestId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The resulting text.
|
||||||
|
/// </summary>
|
||||||
|
public string? TranslatedText { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using FictionArchive.Common.Enums;
|
||||||
|
using FictionArchive.Service.Shared.Services.EventBus;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Models.IntegrationEvents;
|
||||||
|
|
||||||
|
public class TranslationRequestCreatedEvent : IntegrationEvent
|
||||||
|
{
|
||||||
|
public Guid TranslationRequestId { get; set; }
|
||||||
|
public Language From { get; set; }
|
||||||
|
public Language To { get; set; }
|
||||||
|
public string Body { get; set; }
|
||||||
|
public string TranslationEngineKey { get; set; }
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
|
|
||||||
namespace FictionArchive.Service.NovelService.Models.Localization;
|
namespace FictionArchive.Service.NovelService.Models.Localization;
|
||||||
|
|
||||||
public class LocalizationKey : BaseEntity<uint>
|
public class LocalizationKey : BaseEntity<Guid>
|
||||||
{
|
{
|
||||||
public List<LocalizationText> Texts { get; set; }
|
public List<LocalizationText> Texts { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using FictionArchive.Common.Enums;
|
||||||
|
using FictionArchive.Service.NovelService.Models.Novels;
|
||||||
|
using FictionArchive.Service.Shared.Models;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Models.Localization;
|
||||||
|
|
||||||
|
public class LocalizationRequest : BaseEntity<Guid>
|
||||||
|
{
|
||||||
|
public LocalizationKey KeyRequestedForTranslation { get; set; }
|
||||||
|
public Language TranslateTo { get; set; }
|
||||||
|
public TranslationEngine Engine { get; set; }
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ using FictionArchive.Service.Shared.Models;
|
|||||||
|
|
||||||
namespace FictionArchive.Service.NovelService.Models.Localization;
|
namespace FictionArchive.Service.NovelService.Models.Localization;
|
||||||
|
|
||||||
public class LocalizationText : BaseEntity<uint>
|
public class LocalizationText : BaseEntity<Guid>
|
||||||
{
|
{
|
||||||
public Language Language { get; set; }
|
public Language Language { get; set; }
|
||||||
public string Text { get; set; }
|
public string Text { get; set; }
|
||||||
|
|||||||
@@ -5,5 +5,4 @@ namespace FictionArchive.Service.NovelService.Models.Novels;
|
|||||||
public class TranslationEngine : BaseEntity<uint>
|
public class TranslationEngine : BaseEntity<uint>
|
||||||
{
|
{
|
||||||
public string Key { get; set; }
|
public string Key { get; set; }
|
||||||
public string DisplayName { get; set; }
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
using FictionArchive.Service.NovelService.GraphQL;
|
using FictionArchive.Service.NovelService.GraphQL;
|
||||||
|
using FictionArchive.Service.NovelService.Models.IntegrationEvents;
|
||||||
using FictionArchive.Service.NovelService.Services;
|
using FictionArchive.Service.NovelService.Services;
|
||||||
|
using FictionArchive.Service.NovelService.Services.EventHandlers;
|
||||||
using FictionArchive.Service.NovelService.Services.SourceAdapters;
|
using FictionArchive.Service.NovelService.Services.SourceAdapters;
|
||||||
using FictionArchive.Service.NovelService.Services.SourceAdapters.Novelpia;
|
using FictionArchive.Service.NovelService.Services.SourceAdapters.Novelpia;
|
||||||
using FictionArchive.Service.Shared.Extensions;
|
using FictionArchive.Service.Shared.Extensions;
|
||||||
|
using FictionArchive.Service.Shared.Services.EventBus.Implementations;
|
||||||
using FictionArchive.Service.Shared.Services.GraphQL;
|
using FictionArchive.Service.Shared.Services.GraphQL;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
@@ -16,6 +19,18 @@ public class Program
|
|||||||
|
|
||||||
builder.Services.AddMemoryCache();
|
builder.Services.AddMemoryCache();
|
||||||
|
|
||||||
|
#region Event Bus
|
||||||
|
|
||||||
|
builder.Services.AddRabbitMQ(opt =>
|
||||||
|
{
|
||||||
|
builder.Configuration.GetSection("RabbitMQ").Bind(opt);
|
||||||
|
})
|
||||||
|
.Subscribe<TranslationRequestCompletedEvent, TranslationRequestCompletedEventHandler>()
|
||||||
|
.Subscribe<NovelUpdateRequestedEvent, NovelUpdateRequestedEventHandler>()
|
||||||
|
.Subscribe<ChapterPullRequestedEvent, ChapterPullRequestedEventHandler>();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region GraphQL
|
#region GraphQL
|
||||||
|
|
||||||
builder.Services.AddDefaultGraphQl<Query, Mutation>();
|
builder.Services.AddDefaultGraphQl<Query, Mutation>();
|
||||||
@@ -40,6 +55,8 @@ public class Program
|
|||||||
client.BaseAddress = new Uri("https://novelpia.com");
|
client.BaseAddress = new Uri("https://novelpia.com");
|
||||||
})
|
})
|
||||||
.AddHttpMessageHandler<NovelpiaAuthMessageHandler>();
|
.AddHttpMessageHandler<NovelpiaAuthMessageHandler>();
|
||||||
|
|
||||||
|
builder.Services.AddTransient<NovelUpdateService>();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using FictionArchive.Service.NovelService.Models.IntegrationEvents;
|
||||||
|
using FictionArchive.Service.Shared.Services.EventBus;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Services.EventHandlers;
|
||||||
|
|
||||||
|
public class ChapterPullRequestedEventHandler : IIntegrationEventHandler<ChapterPullRequestedEvent>
|
||||||
|
{
|
||||||
|
private readonly NovelUpdateService _novelUpdateService;
|
||||||
|
|
||||||
|
public ChapterPullRequestedEventHandler(NovelUpdateService novelUpdateService)
|
||||||
|
{
|
||||||
|
_novelUpdateService = novelUpdateService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(ChapterPullRequestedEvent @event)
|
||||||
|
{
|
||||||
|
await _novelUpdateService.PullChapterContents(@event.NovelId, @event.ChapterNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using FictionArchive.Service.NovelService.Models.IntegrationEvents;
|
||||||
|
using FictionArchive.Service.Shared.Services.EventBus;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Services.EventHandlers;
|
||||||
|
|
||||||
|
public class NovelUpdateRequestedEventHandler : IIntegrationEventHandler<NovelUpdateRequestedEvent>
|
||||||
|
{
|
||||||
|
private readonly ILogger<NovelUpdateRequestedEventHandler> _logger;
|
||||||
|
private readonly IEventBus _eventBus;
|
||||||
|
private readonly NovelUpdateService _novelUpdateService;
|
||||||
|
|
||||||
|
public NovelUpdateRequestedEventHandler(ILogger<NovelUpdateRequestedEventHandler> logger, IEventBus eventBus, NovelUpdateService novelUpdateService)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_eventBus = eventBus;
|
||||||
|
_novelUpdateService = novelUpdateService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(NovelUpdateRequestedEvent @event)
|
||||||
|
{
|
||||||
|
await _novelUpdateService.ImportNovel(@event.NovelUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using FictionArchive.Service.NovelService.Models.IntegrationEvents;
|
||||||
|
using FictionArchive.Service.NovelService.Models.Localization;
|
||||||
|
using FictionArchive.Service.Shared.Services.EventBus;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Services.EventHandlers;
|
||||||
|
|
||||||
|
public class TranslationRequestCompletedEventHandler : IIntegrationEventHandler<TranslationRequestCompletedEvent>
|
||||||
|
{
|
||||||
|
private readonly ILogger<TranslationRequestCompletedEventHandler> _logger;
|
||||||
|
private readonly NovelServiceDbContext _dbContext;
|
||||||
|
|
||||||
|
public TranslationRequestCompletedEventHandler(ILogger<TranslationRequestCompletedEventHandler> logger, NovelServiceDbContext dbContext)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_dbContext = dbContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(TranslationRequestCompletedEvent @event)
|
||||||
|
{
|
||||||
|
var localizationRequest = await _dbContext.LocalizationRequests.Include(r => r.KeyRequestedForTranslation)
|
||||||
|
.ThenInclude(lk => lk.Texts)
|
||||||
|
.FirstOrDefaultAsync(lk => lk.Id == @event.TranslationRequestId);
|
||||||
|
if (localizationRequest == null)
|
||||||
|
{
|
||||||
|
// Not one of our requests, discard it
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
localizationRequest.KeyRequestedForTranslation.Texts.Add(new LocalizationText()
|
||||||
|
{
|
||||||
|
Language = localizationRequest.TranslateTo,
|
||||||
|
Text = @event.TranslatedText,
|
||||||
|
TranslationEngine = localizationRequest.Engine
|
||||||
|
});
|
||||||
|
_dbContext.LocalizationRequests.Remove(localizationRequest);
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using FictionArchive.Service.NovelService.Models.Localization;
|
||||||
using FictionArchive.Service.NovelService.Models.Novels;
|
using FictionArchive.Service.NovelService.Models.Novels;
|
||||||
using FictionArchive.Service.Shared.Services.Database;
|
using FictionArchive.Service.Shared.Services.Database;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@@ -11,4 +12,6 @@ public class NovelServiceDbContext(DbContextOptions options, ILogger<NovelServic
|
|||||||
public DbSet<Source> Sources { get; set; }
|
public DbSet<Source> Sources { get; set; }
|
||||||
public DbSet<TranslationEngine> TranslationEngines { get; set; }
|
public DbSet<TranslationEngine> TranslationEngines { get; set; }
|
||||||
public DbSet<NovelTag> Tags { get; set; }
|
public DbSet<NovelTag> Tags { get; set; }
|
||||||
|
public DbSet<LocalizationKey> LocalizationKeys { get; set; }
|
||||||
|
public DbSet<LocalizationRequest> LocalizationRequests { get; set; }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
using FictionArchive.Service.NovelService.Models.Enums;
|
||||||
|
using FictionArchive.Service.NovelService.Models.Localization;
|
||||||
|
using FictionArchive.Service.NovelService.Models.Novels;
|
||||||
|
using FictionArchive.Service.NovelService.Models.SourceAdapters;
|
||||||
|
using FictionArchive.Service.NovelService.Services.SourceAdapters;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Services;
|
||||||
|
|
||||||
|
public class NovelUpdateService
|
||||||
|
{
|
||||||
|
private readonly NovelServiceDbContext _dbContext;
|
||||||
|
private readonly ILogger<NovelUpdateService> _logger;
|
||||||
|
private readonly IEnumerable<ISourceAdapter> _sourceAdapters;
|
||||||
|
|
||||||
|
public NovelUpdateService(NovelServiceDbContext dbContext, ILogger<NovelUpdateService> logger, IEnumerable<ISourceAdapter> sourceAdapters)
|
||||||
|
{
|
||||||
|
_dbContext = dbContext;
|
||||||
|
_logger = logger;
|
||||||
|
_sourceAdapters = sourceAdapters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Novel> ImportNovel(string novelUrl)
|
||||||
|
{
|
||||||
|
NovelMetadata? metadata = null;
|
||||||
|
foreach (ISourceAdapter sourceAdapter in _sourceAdapters)
|
||||||
|
{
|
||||||
|
if (await sourceAdapter.CanProcessNovel(novelUrl))
|
||||||
|
{
|
||||||
|
metadata = await sourceAdapter.GetMetadata(novelUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadata == null)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("The provided novel url is currently unsupported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var systemTags = metadata.SystemTags.Select(tag => new NovelTag()
|
||||||
|
{
|
||||||
|
Key = tag,
|
||||||
|
DisplayName = LocalizationKey.CreateFromText(tag, metadata.RawLanguage),
|
||||||
|
TagType = TagType.System
|
||||||
|
});
|
||||||
|
var sourceTags = metadata.SourceTags.Select(tag => new NovelTag()
|
||||||
|
{
|
||||||
|
Key = tag,
|
||||||
|
DisplayName = LocalizationKey.CreateFromText(tag, metadata.RawLanguage),
|
||||||
|
TagType = TagType.External
|
||||||
|
});
|
||||||
|
|
||||||
|
var addedNovel = _dbContext.Novels.Add(new Novel()
|
||||||
|
{
|
||||||
|
Author = new Person()
|
||||||
|
{
|
||||||
|
Name = LocalizationKey.CreateFromText(metadata.AuthorName, metadata.RawLanguage),
|
||||||
|
ExternalUrl = metadata.AuthorUrl,
|
||||||
|
},
|
||||||
|
RawLanguage = metadata.RawLanguage,
|
||||||
|
Url = metadata.Url,
|
||||||
|
ExternalId = metadata.ExternalId,
|
||||||
|
Chapters = metadata.Chapters.Select(chapter =>
|
||||||
|
{
|
||||||
|
return new Chapter()
|
||||||
|
{
|
||||||
|
Order = chapter.Order,
|
||||||
|
Url = chapter.Url,
|
||||||
|
Revision = chapter.Revision,
|
||||||
|
Name = LocalizationKey.CreateFromText(chapter.Name, metadata.RawLanguage),
|
||||||
|
Body = new LocalizationKey()
|
||||||
|
{
|
||||||
|
Texts = new List<LocalizationText>()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}).ToList(),
|
||||||
|
Description = LocalizationKey.CreateFromText(metadata.Description, metadata.RawLanguage),
|
||||||
|
Name = LocalizationKey.CreateFromText(metadata.Name, metadata.RawLanguage),
|
||||||
|
RawStatus = metadata.RawStatus,
|
||||||
|
Tags = sourceTags.Concat(systemTags).ToList(),
|
||||||
|
Source = new Source()
|
||||||
|
{
|
||||||
|
Name = metadata.SourceDescriptor.Name,
|
||||||
|
Url = metadata.SourceDescriptor.Url,
|
||||||
|
Key = metadata.SourceDescriptor.Key,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
return addedNovel.Entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Chapter> PullChapterContents(uint novelId, uint chapterNumber)
|
||||||
|
{
|
||||||
|
var novel = await _dbContext.Novels.Where(novel => novel.Id == novelId)
|
||||||
|
.Include(novel => novel.Chapters)
|
||||||
|
.ThenInclude(chapter => chapter.Body)
|
||||||
|
.ThenInclude(body => body.Texts)
|
||||||
|
.Include(novel => novel.Source)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
var chapter = novel.Chapters.Where(chapter => chapter.Order == chapterNumber).FirstOrDefault();
|
||||||
|
var adapter = _sourceAdapters.FirstOrDefault(adapter => adapter.SourceDescriptor.Key == novel.Source.Key);
|
||||||
|
var rawChapter = await adapter.GetRawChapter(chapter.Url);
|
||||||
|
chapter.Body.Texts.Add(new LocalizationText()
|
||||||
|
{
|
||||||
|
Text = rawChapter,
|
||||||
|
Language = novel.RawLanguage
|
||||||
|
});
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
return chapter;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,5 +12,9 @@
|
|||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"DefaultConnection": "Host=localhost;Database=FictionArchive_NovelService;Username=postgres;password=postgres"
|
"DefaultConnection": "Host=localhost;Database=FictionArchive_NovelService;Username=postgres;password=postgres"
|
||||||
},
|
},
|
||||||
|
"RabbitMQ": {
|
||||||
|
"ConnectionString": "amqp://localhost",
|
||||||
|
"ClientIdentifier": "NovelService"
|
||||||
|
},
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.2.0" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="9.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="9.0.4" />
|
||||||
<PackageReference Include="RabbitMQ.Client" Version="7.2.0" />
|
<PackageReference Include="RabbitMQ.Client" Version="7.2.0" />
|
||||||
@@ -30,9 +32,5 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\FictionArchive.Common\FictionArchive.Common.csproj" />
|
<ProjectReference Include="..\FictionArchive.Common\FictionArchive.Common.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Services\Messaging\Interfaces\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace FictionArchive.Service.Shared.Models;
|
|||||||
|
|
||||||
public abstract class BaseEntity<TKey> : IAuditable
|
public abstract class BaseEntity<TKey> : IAuditable
|
||||||
{
|
{
|
||||||
public uint Id { get; set; }
|
public TKey Id { get; set; }
|
||||||
public Instant CreatedTime { get; set; }
|
public Instant CreatedTime { get; set; }
|
||||||
public Instant LastUpdatedTime { get; set; }
|
public Instant LastUpdatedTime { get; set; }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.Shared.Services.EventBus;
|
||||||
|
|
||||||
|
public class EventBusBuilder<TEventBus> where TEventBus : class, IEventBus
|
||||||
|
{
|
||||||
|
private readonly IServiceCollection _services;
|
||||||
|
private readonly SubscriptionManager _subscriptionManager;
|
||||||
|
|
||||||
|
public EventBusBuilder(IServiceCollection services)
|
||||||
|
{
|
||||||
|
_services = services;
|
||||||
|
_services.AddSingleton<IEventBus, TEventBus>();
|
||||||
|
|
||||||
|
_subscriptionManager = new SubscriptionManager();
|
||||||
|
_services.AddSingleton<SubscriptionManager>(_subscriptionManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventBusBuilder<TEventBus> Subscribe<TEvent, TEventHandler>() where TEvent : IntegrationEvent where TEventHandler : class, IIntegrationEventHandler<TEvent>
|
||||||
|
{
|
||||||
|
_services.AddKeyedTransient<IIntegrationEventHandler, TEventHandler>(typeof(TEvent).Name);
|
||||||
|
_subscriptionManager.RegisterSubscription<TEvent>();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.Shared.Services.EventBus;
|
||||||
|
|
||||||
|
public static class EventBusExtensions
|
||||||
|
{
|
||||||
|
public static EventBusBuilder<TEventBus> AddEventBus<TEventBus>(this IServiceCollection services)
|
||||||
|
where TEventBus : class, IEventBus
|
||||||
|
{
|
||||||
|
return new EventBusBuilder<TEventBus>(services);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace FictionArchive.Service.Shared.Services.EventBus;
|
||||||
|
|
||||||
|
public interface IEventBus
|
||||||
|
{
|
||||||
|
Task Publish<TEvent>(TEvent integrationEvent) where TEvent : IntegrationEvent;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace FictionArchive.Service.Shared.Services.EventBus;
|
||||||
|
|
||||||
|
public interface IIntegrationEventHandler<in TEvent> : IIntegrationEventHandler where TEvent : IntegrationEvent
|
||||||
|
{
|
||||||
|
Task Handle(TEvent @event);
|
||||||
|
Task IIntegrationEventHandler.Handle(IntegrationEvent @event) => Handle((TEvent)@event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IIntegrationEventHandler
|
||||||
|
{
|
||||||
|
Task Handle(IntegrationEvent @event);
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using RabbitMQ.Client;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.Shared.Services.EventBus.Implementations;
|
||||||
|
|
||||||
|
public class RabbitMQConnectionProvider
|
||||||
|
{
|
||||||
|
private readonly IConnectionFactory _connectionFactory;
|
||||||
|
|
||||||
|
private IConnection Connection { get; set; }
|
||||||
|
private IChannel DefaultChannel { get; set; }
|
||||||
|
|
||||||
|
public RabbitMQConnectionProvider(IConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
_connectionFactory = connectionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IConnection> GetConnectionAsync()
|
||||||
|
{
|
||||||
|
if (Connection == null)
|
||||||
|
{
|
||||||
|
Connection = await _connectionFactory.CreateConnectionAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IChannel> GetDefaultChannelAsync()
|
||||||
|
{
|
||||||
|
if (DefaultChannel == null)
|
||||||
|
{
|
||||||
|
DefaultChannel = await (await GetConnectionAsync()).CreateChannelAsync();
|
||||||
|
}
|
||||||
|
return DefaultChannel;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using NodaTime;
|
||||||
|
using NodaTime.Serialization.JsonNet;
|
||||||
|
using RabbitMQ.Client;
|
||||||
|
using RabbitMQ.Client.Events;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.Shared.Services.EventBus.Implementations;
|
||||||
|
|
||||||
|
public class RabbitMQEventBus : IEventBus, IHostedService
|
||||||
|
{
|
||||||
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
|
private readonly RabbitMQConnectionProvider _connectionProvider;
|
||||||
|
private readonly RabbitMQOptions _options;
|
||||||
|
private readonly SubscriptionManager _subscriptionManager;
|
||||||
|
private readonly ILogger<RabbitMQEventBus> _logger;
|
||||||
|
|
||||||
|
private readonly JsonSerializerSettings _jsonSerializerSettings;
|
||||||
|
|
||||||
|
private const string ExchangeName = "fiction-archive-event-bus";
|
||||||
|
|
||||||
|
public RabbitMQEventBus(IServiceScopeFactory serviceScopeFactory, RabbitMQConnectionProvider connectionProvider, IOptions<RabbitMQOptions> options, SubscriptionManager subscriptionManager, ILogger<RabbitMQEventBus> logger)
|
||||||
|
{
|
||||||
|
_serviceScopeFactory = serviceScopeFactory;
|
||||||
|
_connectionProvider = connectionProvider;
|
||||||
|
_subscriptionManager = subscriptionManager;
|
||||||
|
_logger = logger;
|
||||||
|
_options = options.Value;
|
||||||
|
_jsonSerializerSettings = new JsonSerializerSettings().ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Publish<TEvent>(TEvent integrationEvent) where TEvent : IntegrationEvent
|
||||||
|
{
|
||||||
|
var routingKey = typeof(TEvent).Name;
|
||||||
|
var channel = await _connectionProvider.GetDefaultChannelAsync();
|
||||||
|
|
||||||
|
// Set integration event values
|
||||||
|
integrationEvent.CreatedAt = Instant.FromDateTimeUtc(DateTime.UtcNow);
|
||||||
|
integrationEvent.EventId = Guid.NewGuid();
|
||||||
|
|
||||||
|
var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(integrationEvent));
|
||||||
|
await channel.BasicPublishAsync(ExchangeName, routingKey, true, body);
|
||||||
|
_logger.LogInformation("Published event {EventName}", routingKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_ = Task.Factory.StartNew(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var channel = await _connectionProvider.GetDefaultChannelAsync();
|
||||||
|
await channel.ExchangeDeclareAsync(ExchangeName, ExchangeType.Direct,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
await channel.QueueDeclareAsync(_options.ClientIdentifier, true, false, false,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
var consumer = new AsyncEventingBasicConsumer(channel);
|
||||||
|
consumer.ReceivedAsync += (sender, @event) =>
|
||||||
|
{
|
||||||
|
return OnReceivedEvent(sender, @event, channel);
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var subscription in _subscriptionManager.Subscriptions)
|
||||||
|
{
|
||||||
|
await channel.QueueBindAsync(_options.ClientIdentifier, ExchangeName, subscription.Key,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
_logger.LogInformation("Subscribed to {SubscriptionKey}", subscription.Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
await channel.BasicConsumeAsync(_options.ClientIdentifier, false, consumer, cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
_logger.LogInformation("RabbitMQ EventBus started.");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "An error occurred while starting the RabbitMQ EventBus");
|
||||||
|
}
|
||||||
|
}, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnReceivedEvent(object sender, BasicDeliverEventArgs @event, IChannel channel)
|
||||||
|
{
|
||||||
|
var eventName = @event.RoutingKey;
|
||||||
|
_logger.LogInformation("Received event {EventName}", eventName);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_subscriptionManager.Subscriptions.ContainsKey(eventName))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Received event without subscription entry.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var eventBody = Encoding.UTF8.GetString(@event.Body.Span);
|
||||||
|
var eventObject = JsonConvert.DeserializeObject(eventBody, _subscriptionManager.Subscriptions[eventName], _jsonSerializerSettings) as IntegrationEvent;
|
||||||
|
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
|
||||||
|
foreach (var service in scope.ServiceProvider.GetKeyedServices<IIntegrationEventHandler>(eventName))
|
||||||
|
{
|
||||||
|
await service.Handle(eventObject);
|
||||||
|
}
|
||||||
|
_logger.LogInformation("Finished handling event with name {EventName}", eventName);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "An error occurred while handling an event.");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await channel.BasicAckAsync(@event.DeliveryTag, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using RabbitMQ.Client;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.Shared.Services.EventBus.Implementations;
|
||||||
|
|
||||||
|
public static class RabbitMQExtensions
|
||||||
|
{
|
||||||
|
public static EventBusBuilder<RabbitMQEventBus> AddRabbitMQ(this IServiceCollection services, Action<RabbitMQOptions> configure)
|
||||||
|
{
|
||||||
|
services.Configure(configure);
|
||||||
|
services.AddSingleton<IConnectionFactory, ConnectionFactory>(provider =>
|
||||||
|
{
|
||||||
|
var options = provider.GetService<IOptions<RabbitMQOptions>>();
|
||||||
|
ConnectionFactory factory = new ConnectionFactory();
|
||||||
|
factory.Uri = new Uri(options.Value.ConnectionString);
|
||||||
|
return factory;
|
||||||
|
});
|
||||||
|
services.AddSingleton<RabbitMQConnectionProvider>();
|
||||||
|
services.AddHostedService<RabbitMQEventBus>();
|
||||||
|
return services.AddEventBus<RabbitMQEventBus>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace FictionArchive.Service.Shared.Services.EventBus.Implementations;
|
||||||
|
|
||||||
|
public class RabbitMQOptions
|
||||||
|
{
|
||||||
|
public string ConnectionString { get; set; }
|
||||||
|
public string ClientIdentifier { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.Shared.Services.EventBus;
|
||||||
|
|
||||||
|
public abstract class IntegrationEvent
|
||||||
|
{
|
||||||
|
public Guid EventId { get; set; }
|
||||||
|
public Instant CreatedAt { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace FictionArchive.Service.Shared.Services.EventBus;
|
||||||
|
|
||||||
|
public class SubscriptionManager
|
||||||
|
{
|
||||||
|
public Dictionary<string, Type> Subscriptions { get; } = new Dictionary<string, Type>();
|
||||||
|
|
||||||
|
public void RegisterSubscription<TEvent>()
|
||||||
|
{
|
||||||
|
Subscriptions.Add(typeof(TEvent).Name, typeof(TEvent));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
using FictionArchive.Common.Enums;
|
using FictionArchive.Common.Enums;
|
||||||
|
using FictionArchive.Service.TranslationService.Models;
|
||||||
using FictionArchive.Service.TranslationService.Models.Database;
|
using FictionArchive.Service.TranslationService.Models.Database;
|
||||||
using FictionArchive.Service.TranslationService.Models.Enums;
|
using FictionArchive.Service.TranslationService.Models.Enums;
|
||||||
|
using FictionArchive.Service.TranslationService.Services;
|
||||||
using FictionArchive.Service.TranslationService.Services.Database;
|
using FictionArchive.Service.TranslationService.Services.Database;
|
||||||
using FictionArchive.Service.TranslationService.Services.TranslationEngines;
|
using FictionArchive.Service.TranslationService.Services.TranslationEngines;
|
||||||
|
|
||||||
@@ -8,23 +10,10 @@ namespace FictionArchive.Service.TranslationService.GraphQL;
|
|||||||
|
|
||||||
public class Mutation
|
public class Mutation
|
||||||
{
|
{
|
||||||
public async Task<string> TranslateText(string text, Language from, Language to, string translationEngineKey, IEnumerable<ITranslationEngine> translationEngines, TranslationServiceDbContext dbContext)
|
public async Task<TranslationResult> TranslateText(string text, Language from, Language to, string translationEngineKey, TranslationEngineService translationEngineService)
|
||||||
{
|
{
|
||||||
var engine = translationEngines.FirstOrDefault(engine => engine.Descriptor.Key == translationEngineKey);
|
var result = await translationEngineService.Translate(from, to, text, 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;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using FictionArchive.Common.Enums;
|
||||||
|
using FictionArchive.Service.Shared.Services.EventBus;
|
||||||
|
using FictionArchive.Service.TranslationService.Models.Enums;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.TranslationService.Models.IntegrationEvents;
|
||||||
|
|
||||||
|
public class TranslationRequestCompletedEvent : IntegrationEvent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Maps this event back to a triggering request.
|
||||||
|
/// </summary>
|
||||||
|
public Guid? TranslationRequestId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The resulting text.
|
||||||
|
/// </summary>
|
||||||
|
public string? TranslatedText { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using FictionArchive.Common.Enums;
|
||||||
|
using FictionArchive.Service.Shared.Services.EventBus;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.TranslationService.Models.IntegrationEvents;
|
||||||
|
|
||||||
|
public class TranslationRequestCreatedEvent : IntegrationEvent
|
||||||
|
{
|
||||||
|
public Guid TranslationRequestId { get; set; }
|
||||||
|
public Language From { get; set; }
|
||||||
|
public Language To { get; set; }
|
||||||
|
public string Body { get; set; }
|
||||||
|
public string TranslationEngineKey { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using FictionArchive.Common.Enums;
|
||||||
|
using FictionArchive.Service.TranslationService.Models.Enums;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.TranslationService.Models;
|
||||||
|
|
||||||
|
public class TranslationResult
|
||||||
|
{
|
||||||
|
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; }
|
||||||
|
}
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
using DeepL;
|
using DeepL;
|
||||||
using FictionArchive.Common.Extensions;
|
using FictionArchive.Common.Extensions;
|
||||||
using FictionArchive.Service.Shared.Extensions;
|
using FictionArchive.Service.Shared.Extensions;
|
||||||
|
using FictionArchive.Service.Shared.Services.EventBus.Implementations;
|
||||||
using FictionArchive.Service.Shared.Services.GraphQL;
|
using FictionArchive.Service.Shared.Services.GraphQL;
|
||||||
using FictionArchive.Service.TranslationService.GraphQL;
|
using FictionArchive.Service.TranslationService.GraphQL;
|
||||||
|
using FictionArchive.Service.TranslationService.Models.IntegrationEvents;
|
||||||
|
using FictionArchive.Service.TranslationService.Services;
|
||||||
using FictionArchive.Service.TranslationService.Services.Database;
|
using FictionArchive.Service.TranslationService.Services.Database;
|
||||||
|
using FictionArchive.Service.TranslationService.Services.EventHandlers;
|
||||||
using FictionArchive.Service.TranslationService.Services.TranslationEngines;
|
using FictionArchive.Service.TranslationService.Services.TranslationEngines;
|
||||||
using FictionArchive.Service.TranslationService.Services.TranslationEngines.DeepLTranslate;
|
using FictionArchive.Service.TranslationService.Services.TranslationEngines.DeepLTranslate;
|
||||||
|
using RabbitMQ.Client;
|
||||||
|
|
||||||
namespace FictionArchive.Service.TranslationService;
|
namespace FictionArchive.Service.TranslationService;
|
||||||
|
|
||||||
@@ -18,6 +23,17 @@ public class Program
|
|||||||
|
|
||||||
builder.Services.AddHealthChecks();
|
builder.Services.AddHealthChecks();
|
||||||
|
|
||||||
|
#region Event Bus
|
||||||
|
|
||||||
|
builder.Services.AddRabbitMQ(opt =>
|
||||||
|
{
|
||||||
|
builder.Configuration.GetSection("RabbitMQ").Bind(opt);
|
||||||
|
})
|
||||||
|
.Subscribe<TranslationRequestCreatedEvent, TranslationRequestCreatedEventHandler>();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
#region Database
|
#region Database
|
||||||
|
|
||||||
builder.Services.RegisterDbContext<TranslationServiceDbContext>(builder.Configuration.GetConnectionString("DefaultConnection"));
|
builder.Services.RegisterDbContext<TranslationServiceDbContext>(builder.Configuration.GetConnectionString("DefaultConnection"));
|
||||||
@@ -37,6 +53,8 @@ public class Program
|
|||||||
return new DeepLClient(builder.Configuration["DeepL:ApiKey"]);
|
return new DeepLClient(builder.Configuration["DeepL:ApiKey"]);
|
||||||
});
|
});
|
||||||
builder.Services.AddTransient<ITranslationEngine, DeepLTranslationEngine>();
|
builder.Services.AddTransient<ITranslationEngine, DeepLTranslationEngine>();
|
||||||
|
|
||||||
|
builder.Services.AddTransient<TranslationEngineService>();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using FictionArchive.Service.Shared.Services.EventBus;
|
||||||
|
using FictionArchive.Service.TranslationService.Models.Enums;
|
||||||
|
using FictionArchive.Service.TranslationService.Models.IntegrationEvents;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.TranslationService.Services.EventHandlers;
|
||||||
|
|
||||||
|
public class TranslationRequestCreatedEventHandler : IIntegrationEventHandler<TranslationRequestCreatedEvent>
|
||||||
|
{
|
||||||
|
private readonly ILogger<TranslationRequestCreatedEventHandler> _logger;
|
||||||
|
private readonly TranslationEngineService _translationEngineService;
|
||||||
|
private readonly IEventBus _eventBus;
|
||||||
|
|
||||||
|
public TranslationRequestCreatedEventHandler(ILogger<TranslationRequestCreatedEventHandler> logger, TranslationEngineService translationEngineService)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_translationEngineService = translationEngineService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(TranslationRequestCreatedEvent @event)
|
||||||
|
{
|
||||||
|
var result = await _translationEngineService.Translate(@event.From, @event.To, @event.Body, @event.TranslationEngineKey);
|
||||||
|
if (result.Status == TranslationRequestStatus.Success)
|
||||||
|
{
|
||||||
|
await _eventBus.Publish(new TranslationRequestCompletedEvent()
|
||||||
|
{
|
||||||
|
TranslatedText = result.TranslatedText,
|
||||||
|
TranslationRequestId = @event.TranslationRequestId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using System.Text;
|
||||||
|
using FictionArchive.Common.Enums;
|
||||||
|
using FictionArchive.Service.Shared.Services.EventBus;
|
||||||
|
using FictionArchive.Service.Shared.Services.EventBus.Implementations;
|
||||||
|
using FictionArchive.Service.TranslationService.Models;
|
||||||
|
using FictionArchive.Service.TranslationService.Models.Database;
|
||||||
|
using FictionArchive.Service.TranslationService.Models.Enums;
|
||||||
|
using FictionArchive.Service.TranslationService.Models.IntegrationEvents;
|
||||||
|
using FictionArchive.Service.TranslationService.Services.Database;
|
||||||
|
using FictionArchive.Service.TranslationService.Services.TranslationEngines;
|
||||||
|
using RabbitMQ.Client;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.TranslationService.Services;
|
||||||
|
|
||||||
|
public class TranslationEngineService
|
||||||
|
{
|
||||||
|
private readonly IEnumerable<ITranslationEngine> _translationEngines;
|
||||||
|
private readonly IEventBus _eventBus;
|
||||||
|
private readonly TranslationServiceDbContext _dbContext;
|
||||||
|
|
||||||
|
public TranslationEngineService(IEnumerable<ITranslationEngine> translationEngines, TranslationServiceDbContext dbContext, IEventBus eventBus)
|
||||||
|
{
|
||||||
|
_translationEngines = translationEngines;
|
||||||
|
_dbContext = dbContext;
|
||||||
|
_eventBus = eventBus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TranslationResult> Translate(Language from, Language to, string text, string translationEngineKey)
|
||||||
|
{
|
||||||
|
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 = translation.BilledCharacterCount, // FILL ME
|
||||||
|
From = from,
|
||||||
|
To = to,
|
||||||
|
Status = translation != null ? TranslationRequestStatus.Success : TranslationRequestStatus.Failed,
|
||||||
|
TranslatedText = translation.TranslatedText,
|
||||||
|
TranslationEngineKey = translationEngineKey
|
||||||
|
});
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
return translation;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using DeepL;
|
using DeepL;
|
||||||
using DeepL.Model;
|
using DeepL.Model;
|
||||||
using FictionArchive.Service.TranslationService.Models;
|
using FictionArchive.Service.TranslationService.Models;
|
||||||
|
using FictionArchive.Service.TranslationService.Models.Enums;
|
||||||
using Language = FictionArchive.Common.Enums.Language;
|
using Language = FictionArchive.Common.Enums.Language;
|
||||||
|
|
||||||
namespace FictionArchive.Service.TranslationService.Services.TranslationEngines.DeepLTranslate;
|
namespace FictionArchive.Service.TranslationService.Services.TranslationEngines.DeepLTranslate;
|
||||||
@@ -31,11 +32,20 @@ public class DeepLTranslationEngine : ITranslationEngine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string?> GetTranslation(string body, Language from, Language to)
|
public async Task<TranslationResult> GetTranslation(string body, Language from, Language to)
|
||||||
{
|
{
|
||||||
TextResult translationResult = await _deepLClient.TranslateTextAsync(body, GetLanguageCode(from), GetLanguageCode(to));
|
TextResult translationResult = await _deepLClient.TranslateTextAsync(body, GetLanguageCode(from), GetLanguageCode(to));
|
||||||
_logger.LogInformation("Translated text. Usage statistics: CHARACTERS BILLED {TranslationResultBilledCharacters}", translationResult.BilledCharacters);
|
_logger.LogInformation("Translated text. Usage statistics: CHARACTERS BILLED {TranslationResultBilledCharacters}", translationResult.BilledCharacters);
|
||||||
return translationResult.Text;
|
return new TranslationResult()
|
||||||
|
{
|
||||||
|
OriginalText = body,
|
||||||
|
From = from,
|
||||||
|
To = to,
|
||||||
|
TranslationEngineKey = Key,
|
||||||
|
BilledCharacterCount = (uint)translationResult.BilledCharacters,
|
||||||
|
Status = TranslationRequestStatus.Success,
|
||||||
|
TranslatedText = translationResult.Text
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetLanguageCode(Language language)
|
private string GetLanguageCode(Language language)
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ namespace FictionArchive.Service.TranslationService.Services.TranslationEngines;
|
|||||||
public interface ITranslationEngine
|
public interface ITranslationEngine
|
||||||
{
|
{
|
||||||
public TranslationEngineDescriptor Descriptor { get; }
|
public TranslationEngineDescriptor Descriptor { get; }
|
||||||
public Task<string?> GetTranslation(string body, Language from, Language to);
|
public Task<TranslationResult> GetTranslation(string body, Language from, Language to);
|
||||||
}
|
}
|
||||||
@@ -11,5 +11,9 @@
|
|||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"DefaultConnection": "Host=localhost;Database=FictionArchive_NovelService;Username=postgres;password=postgres"
|
"DefaultConnection": "Host=localhost;Database=FictionArchive_NovelService;Username=postgres;password=postgres"
|
||||||
},
|
},
|
||||||
|
"RabbitMQ": {
|
||||||
|
"ConnectionString": "amqp://localhost",
|
||||||
|
"ClientIdentifier": "TranslationService"
|
||||||
|
},
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user