Initial commit
This commit is contained in:
25
.dockerignore
Normal file
25
.dockerignore
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/.idea
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/azds.yaml
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
133
.gitignore
vendored
Normal file
133
.gitignore
vendored
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
|
||||||
|
[Dd]ebug/
|
||||||
|
[Rr]elease/
|
||||||
|
x64/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_i.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.log
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.Publish.xml
|
||||||
|
*.pubxml
|
||||||
|
*.azurePubxml
|
||||||
|
|
||||||
|
# NuGet Packages Directory
|
||||||
|
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
|
||||||
|
packages/
|
||||||
|
## TODO: If the tool you use requires repositories.config, also uncomment the next line
|
||||||
|
!packages/repositories.config
|
||||||
|
|
||||||
|
# Windows Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Windows Store app package directory
|
||||||
|
AppPackages/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
sql/
|
||||||
|
*.Cache
|
||||||
|
ClientBin/
|
||||||
|
[Ss]tyle[Cc]op.*
|
||||||
|
![Ss]tyle[Cc]op.targets
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
|
||||||
|
*.publishsettings
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file to a newer
|
||||||
|
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
App_Data/*.mdf
|
||||||
|
App_Data/*.ldf
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Windows detritus
|
||||||
|
# =========================
|
||||||
|
|
||||||
|
# Windows image file caches
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Mac desktop service store files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
_NCrunch*
|
||||||
75
FictionArchive.API/Controllers/TestController.cs
Normal file
75
FictionArchive.API/Controllers/TestController.cs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace FictionArchive.API.Controllers
|
||||||
|
{
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class TestController : ControllerBase
|
||||||
|
{
|
||||||
|
/*private readonly FictionArchiveDbContext _dbContext;
|
||||||
|
private readonly ISourceAdapter _novelpiaAdapter;
|
||||||
|
private readonly ITranslationEngineAdapter _translationEngine;
|
||||||
|
|
||||||
|
public TestController(ISourceAdapter novelpiaAdapter, FictionArchiveDbContext dbContext, ITranslationEngineAdapter translationEngine)
|
||||||
|
{
|
||||||
|
_novelpiaAdapter = novelpiaAdapter;
|
||||||
|
_dbContext = dbContext;
|
||||||
|
_translationEngine = translationEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("GetNovel")]
|
||||||
|
public async Task<Novel> GetNovel(string novelUrl)
|
||||||
|
{
|
||||||
|
var novel = await _novelpiaAdapter.GetMetadata(novelUrl);
|
||||||
|
novel.Source = new Source()
|
||||||
|
{
|
||||||
|
Name = "Novelpia",
|
||||||
|
Id = 1,
|
||||||
|
Url = "https://novelpia.com"
|
||||||
|
};
|
||||||
|
_dbContext.Novels.Add(novel);
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
return novel;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("GetChapter")]
|
||||||
|
public async Task<string> GetChapter(uint novelId, uint chapterNumber)
|
||||||
|
{
|
||||||
|
var novel = await _dbContext.Novels.Include(n => n.Chapters).ThenInclude(c => c.Translations).FirstOrDefaultAsync(n => n.Id == novelId);
|
||||||
|
var chapter = novel.Chapters.FirstOrDefault(c => c.Order == chapterNumber);
|
||||||
|
var rawChapter = await _novelpiaAdapter.GetRawChapter(chapter.Url);
|
||||||
|
chapter.Translations.Add(new ChapterTranslation()
|
||||||
|
{
|
||||||
|
Language = novel.RawLanguage,
|
||||||
|
Body = rawChapter
|
||||||
|
});
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
return rawChapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("TranslateChapter")]
|
||||||
|
public async Task<ChapterTranslation> TranslateChapter(uint novelId, uint chapterNumber, Language to)
|
||||||
|
{
|
||||||
|
var novel = await _dbContext.Novels.Include(n => n.Chapters)
|
||||||
|
.ThenInclude(c => c.Translations).FirstOrDefaultAsync(novel => novel.Id == novelId);
|
||||||
|
var chapter = novel.Chapters.FirstOrDefault(c => c.Order == chapterNumber);
|
||||||
|
var chapterRaw = chapter.Translations.FirstOrDefault(ct => ct.Language == novel.RawLanguage);
|
||||||
|
var newTranslation = new ChapterTranslation()
|
||||||
|
{
|
||||||
|
Language = to,
|
||||||
|
TranslationEngine = new TranslationEngine()
|
||||||
|
{
|
||||||
|
Name = "DeepL"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var translation = await _translationEngine.GetTranslation(chapterRaw.Body, novel.RawLanguage, to);
|
||||||
|
newTranslation.Body = translation;
|
||||||
|
chapter.Translations.Add(newTranslation);
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
return newTranslation;
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
23
FictionArchive.API/Dockerfile
Normal file
23
FictionArchive.API/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||||
|
USER $APP_UID
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 8080
|
||||||
|
EXPOSE 8081
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["FictionArchive.API/FictionArchive.API.csproj", "FictionArchive.API/"]
|
||||||
|
RUN dotnet restore "FictionArchive.API/FictionArchive.API.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/FictionArchive.API"
|
||||||
|
RUN dotnet build "./FictionArchive.API.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
RUN dotnet publish "./FictionArchive.API.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "FictionArchive.API.dll"]
|
||||||
35
FictionArchive.API/FictionArchive.API.csproj
Normal file
35
FictionArchive.API/FictionArchive.API.csproj
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="HotChocolate.AspNetCore" Version="15.1.11" />
|
||||||
|
<PackageReference Include="HotChocolate.Data" Version="15.1.11" />
|
||||||
|
<PackageReference Include="HotChocolate.Data.EntityFramework" Version="15.1.11" />
|
||||||
|
<PackageReference Include="HotChocolate.Types.Scalars" Version="15.1.11" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.11" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.7" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="..\.dockerignore">
|
||||||
|
<Link>.dockerignore</Link>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Controllers\" />
|
||||||
|
<Folder Include="GraphQL\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
6
FictionArchive.API/FictionArchive.API.http
Normal file
6
FictionArchive.API/FictionArchive.API.http
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
@FictionArchive.API_HostAddress = http://localhost:5234
|
||||||
|
|
||||||
|
GET {{FictionArchive.API_HostAddress}}/weatherforecast/
|
||||||
|
Accept: application/json
|
||||||
|
|
||||||
|
###
|
||||||
48
FictionArchive.API/Program.cs
Normal file
48
FictionArchive.API/Program.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
namespace FictionArchive.API;
|
||||||
|
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// Add services to the container.
|
||||||
|
|
||||||
|
// OpenAPI & REST
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
builder.Services.AddMemoryCache();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
builder.Services.AddHealthChecks();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Configure the HTTP request pipeline.
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.UseAuthentication();
|
||||||
|
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.MapGraphQL();
|
||||||
|
|
||||||
|
app.MapHealthChecks("/healthz");
|
||||||
|
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
41
FictionArchive.API/Properties/launchSettings.json
Normal file
41
FictionArchive.API/Properties/launchSettings.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:50224",
|
||||||
|
"sslPort": 44385
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"applicationUrl": "http://localhost:5234",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"applicationUrl": "https://localhost:7063;http://localhost:5234",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
FictionArchive.API/appsettings.Development.json
Normal file
8
FictionArchive.API/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
FictionArchive.API/appsettings.json
Normal file
9
FictionArchive.API/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
9
FictionArchive.Common/Enums/Language.cs
Normal file
9
FictionArchive.Common/Enums/Language.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace FictionArchive.Common.Enums;
|
||||||
|
|
||||||
|
public enum Language
|
||||||
|
{
|
||||||
|
En,
|
||||||
|
Kr,
|
||||||
|
Ch,
|
||||||
|
Ja
|
||||||
|
}
|
||||||
14
FictionArchive.Common/FictionArchive.Common.csproj
Normal file
14
FictionArchive.Common/FictionArchive.Common.csproj
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="HotChocolate.Abstractions" Version="15.1.11" />
|
||||||
|
<PackageReference Include="HotChocolate.Types" Version="15.1.11" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace FictionArchive.Service.NovelService.Constants;
|
||||||
|
|
||||||
|
public static class SystemTags
|
||||||
|
{
|
||||||
|
public const string Nsfw = "Nsfw";
|
||||||
|
}
|
||||||
23
FictionArchive.Service.NovelService/Dockerfile
Normal file
23
FictionArchive.Service.NovelService/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||||
|
USER $APP_UID
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 8080
|
||||||
|
EXPOSE 8081
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["FictionArchive.Service.NovelService/FictionArchive.Service.NovelService.csproj", "FictionArchive.Service.NovelService/"]
|
||||||
|
RUN dotnet restore "FictionArchive.Service.NovelService/FictionArchive.Service.NovelService.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/FictionArchive.Service.NovelService"
|
||||||
|
RUN dotnet build "./FictionArchive.Service.NovelService.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
RUN dotnet publish "./FictionArchive.Service.NovelService.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "FictionArchive.Service.NovelService.dll"]
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="HotChocolate.AspNetCore" Version="15.1.11" />
|
||||||
|
<PackageReference Include="HotChocolate.Data.EntityFramework" Version="15.1.11" />
|
||||||
|
<PackageReference Include="HotChocolate.Types.Scalars" Version="15.1.11" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.11" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.11">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="..\.dockerignore">
|
||||||
|
<Link>.dockerignore</Link>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\FictionArchive.Common\FictionArchive.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\FictionArchive.Service.Shared\FictionArchive.Service.Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Models\Interfaces\" />
|
||||||
|
<Folder Include="Services\GraphQL\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
107
FictionArchive.Service.NovelService/GraphQL/Mutation.cs
Normal file
107
FictionArchive.Service.NovelService/GraphQL/Mutation.cs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
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;
|
||||||
|
using FictionArchive.Service.NovelService.Services.SourceAdapters;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.GraphQL;
|
||||||
|
|
||||||
|
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
|
||||||
|
// 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;
|
||||||
|
foreach (ISourceAdapter sourceAdapter in adapters)
|
||||||
|
{
|
||||||
|
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 = metadata.AuthorName,
|
||||||
|
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,
|
||||||
|
uint chapterNumber,
|
||||||
|
NovelServiceDbContext dbContext,
|
||||||
|
IEnumerable<ISourceAdapter> sourceAdapters)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
FictionArchive.Service.NovelService/GraphQL/Query.cs
Normal file
16
FictionArchive.Service.NovelService/GraphQL/Query.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using FictionArchive.Service.NovelService.Models.Novels;
|
||||||
|
using FictionArchive.Service.NovelService.Services;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.GraphQL;
|
||||||
|
|
||||||
|
public class Query
|
||||||
|
{
|
||||||
|
[UsePaging]
|
||||||
|
[UseProjection]
|
||||||
|
[UseFiltering]
|
||||||
|
[UseSorting]
|
||||||
|
public IQueryable<Novel> GetNovels(NovelServiceDbContext dbContext)
|
||||||
|
{
|
||||||
|
return dbContext.Novels.AsQueryable();
|
||||||
|
}
|
||||||
|
}
|
||||||
409
FictionArchive.Service.NovelService/Migrations/20251118021857_Initial.Designer.cs
generated
Normal file
409
FictionArchive.Service.NovelService/Migrations/20251118021857_Initial.Designer.cs
generated
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using FictionArchive.Service.NovelService.Services;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(NovelServiceDbContext))]
|
||||||
|
[Migration("20251118021857_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.NovelService.Models.Localization.LocalizationKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("LocalizationKey");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationText", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int>("Language")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<long?>("LocalizationKeyId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Text")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long?>("TranslationEngineId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("LocalizationKeyId");
|
||||||
|
|
||||||
|
b.HasIndex("TranslationEngineId");
|
||||||
|
|
||||||
|
b.ToTable("LocalizationText");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Chapter", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("BodyId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("NameId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long?>("NovelId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("Order")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("Revision")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BodyId");
|
||||||
|
|
||||||
|
b.HasIndex("NameId");
|
||||||
|
|
||||||
|
b.HasIndex("NovelId");
|
||||||
|
|
||||||
|
b.ToTable("Chapter");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Novel", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("DescriptionId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("ExternalId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long>("NameId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("RawLanguage")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("RawStatus")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<long>("SourceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int?>("StatusOverride")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorId");
|
||||||
|
|
||||||
|
b.HasIndex("DescriptionId");
|
||||||
|
|
||||||
|
b.HasIndex("NameId");
|
||||||
|
|
||||||
|
b.HasIndex("SourceId");
|
||||||
|
|
||||||
|
b.ToTable("Novels");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.NovelTag", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("DisplayNameId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long?>("NovelId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long?>("SourceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("TagType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DisplayNameId");
|
||||||
|
|
||||||
|
b.HasIndex("NovelId");
|
||||||
|
|
||||||
|
b.HasIndex("SourceId");
|
||||||
|
|
||||||
|
b.ToTable("Tags");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Person", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("ExternalUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Person");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Source", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Sources");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.TranslationEngine", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("TranslationEngines");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationText", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", null)
|
||||||
|
.WithMany("Texts")
|
||||||
|
.HasForeignKey("LocalizationKeyId");
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.TranslationEngine", "TranslationEngine")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("TranslationEngineId");
|
||||||
|
|
||||||
|
b.Navigation("TranslationEngine");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Chapter", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "Body")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("BodyId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "Name")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("NameId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Novel", null)
|
||||||
|
.WithMany("Chapters")
|
||||||
|
.HasForeignKey("NovelId");
|
||||||
|
|
||||||
|
b.Navigation("Body");
|
||||||
|
|
||||||
|
b.Navigation("Name");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Novel", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Person", "Author")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AuthorId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "Description")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DescriptionId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "Name")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("NameId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Source", "Source")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SourceId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Author");
|
||||||
|
|
||||||
|
b.Navigation("Description");
|
||||||
|
|
||||||
|
b.Navigation("Name");
|
||||||
|
|
||||||
|
b.Navigation("Source");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.NovelTag", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "DisplayName")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DisplayNameId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Novel", null)
|
||||||
|
.WithMany("Tags")
|
||||||
|
.HasForeignKey("NovelId");
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Source", "Source")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SourceId");
|
||||||
|
|
||||||
|
b.Navigation("DisplayName");
|
||||||
|
|
||||||
|
b.Navigation("Source");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Texts");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Novel", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Chapters");
|
||||||
|
|
||||||
|
b.Navigation("Tags");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,313 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Initial : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "LocalizationKey",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedUtc = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
UpdatedUtc = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_LocalizationKey", 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),
|
||||||
|
CreatedUtc = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
UpdatedUtc = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Person", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Sources",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
Name = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Url = table.Column<string>(type: "text", nullable: false),
|
||||||
|
CreatedUtc = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
UpdatedUtc = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Sources", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "TranslationEngines",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
Key = table.Column<string>(type: "text", nullable: false),
|
||||||
|
DisplayName = table.Column<string>(type: "text", nullable: false),
|
||||||
|
CreatedUtc = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
UpdatedUtc = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_TranslationEngines", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Novels",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
AuthorId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Url = table.Column<string>(type: "text", nullable: false),
|
||||||
|
RawLanguage = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
RawStatus = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
StatusOverride = table.Column<int>(type: "integer", nullable: true),
|
||||||
|
SourceId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
ExternalId = table.Column<string>(type: "text", nullable: false),
|
||||||
|
NameId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
DescriptionId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
CreatedUtc = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
UpdatedUtc = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Novels", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Novels_LocalizationKey_DescriptionId",
|
||||||
|
column: x => x.DescriptionId,
|
||||||
|
principalTable: "LocalizationKey",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Novels_LocalizationKey_NameId",
|
||||||
|
column: x => x.NameId,
|
||||||
|
principalTable: "LocalizationKey",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Novels_Person_AuthorId",
|
||||||
|
column: x => x.AuthorId,
|
||||||
|
principalTable: "Person",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Novels_Sources_SourceId",
|
||||||
|
column: x => x.SourceId,
|
||||||
|
principalTable: "Sources",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
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),
|
||||||
|
CreatedUtc = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
UpdatedUtc = table.Column<DateTime>(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(
|
||||||
|
name: "Chapter",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
Revision = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Order = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Url = table.Column<string>(type: "text", nullable: true),
|
||||||
|
NameId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
BodyId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
NovelId = table.Column<long>(type: "bigint", nullable: true),
|
||||||
|
CreatedUtc = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
UpdatedUtc = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Chapter", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Chapter_LocalizationKey_BodyId",
|
||||||
|
column: x => x.BodyId,
|
||||||
|
principalTable: "LocalizationKey",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Chapter_LocalizationKey_NameId",
|
||||||
|
column: x => x.NameId,
|
||||||
|
principalTable: "LocalizationKey",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Chapter_Novels_NovelId",
|
||||||
|
column: x => x.NovelId,
|
||||||
|
principalTable: "Novels",
|
||||||
|
principalColumn: "Id");
|
||||||
|
});
|
||||||
|
|
||||||
|
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),
|
||||||
|
NovelId = table.Column<long>(type: "bigint", nullable: true),
|
||||||
|
CreatedUtc = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
UpdatedUtc = table.Column<DateTime>(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_Novels_NovelId",
|
||||||
|
column: x => x.NovelId,
|
||||||
|
principalTable: "Novels",
|
||||||
|
principalColumn: "Id");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Tags_Sources_SourceId",
|
||||||
|
column: x => x.SourceId,
|
||||||
|
principalTable: "Sources",
|
||||||
|
principalColumn: "Id");
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Chapter_BodyId",
|
||||||
|
table: "Chapter",
|
||||||
|
column: "BodyId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Chapter_NameId",
|
||||||
|
table: "Chapter",
|
||||||
|
column: "NameId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Chapter_NovelId",
|
||||||
|
table: "Chapter",
|
||||||
|
column: "NovelId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_LocalizationText_LocalizationKeyId",
|
||||||
|
table: "LocalizationText",
|
||||||
|
column: "LocalizationKeyId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_LocalizationText_TranslationEngineId",
|
||||||
|
table: "LocalizationText",
|
||||||
|
column: "TranslationEngineId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Novels_AuthorId",
|
||||||
|
table: "Novels",
|
||||||
|
column: "AuthorId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Novels_DescriptionId",
|
||||||
|
table: "Novels",
|
||||||
|
column: "DescriptionId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Novels_NameId",
|
||||||
|
table: "Novels",
|
||||||
|
column: "NameId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Novels_SourceId",
|
||||||
|
table: "Novels",
|
||||||
|
column: "SourceId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Tags_DisplayNameId",
|
||||||
|
table: "Tags",
|
||||||
|
column: "DisplayNameId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Tags_NovelId",
|
||||||
|
table: "Tags",
|
||||||
|
column: "NovelId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Tags_SourceId",
|
||||||
|
table: "Tags",
|
||||||
|
column: "SourceId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Chapter");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "LocalizationText");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Tags");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "TranslationEngines");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Novels");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "LocalizationKey");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Person");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Sources");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
413
FictionArchive.Service.NovelService/Migrations/20251118023157_AddSourceKey.Designer.cs
generated
Normal file
413
FictionArchive.Service.NovelService/Migrations/20251118023157_AddSourceKey.Designer.cs
generated
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using FictionArchive.Service.NovelService.Services;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(NovelServiceDbContext))]
|
||||||
|
[Migration("20251118023157_AddSourceKey")]
|
||||||
|
partial class AddSourceKey
|
||||||
|
{
|
||||||
|
/// <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.NovelService.Models.Localization.LocalizationKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("LocalizationKey");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationText", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int>("Language")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<long?>("LocalizationKeyId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Text")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long?>("TranslationEngineId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("LocalizationKeyId");
|
||||||
|
|
||||||
|
b.HasIndex("TranslationEngineId");
|
||||||
|
|
||||||
|
b.ToTable("LocalizationText");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Chapter", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("BodyId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("NameId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long?>("NovelId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("Order")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("Revision")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BodyId");
|
||||||
|
|
||||||
|
b.HasIndex("NameId");
|
||||||
|
|
||||||
|
b.HasIndex("NovelId");
|
||||||
|
|
||||||
|
b.ToTable("Chapter");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Novel", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("DescriptionId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("ExternalId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long>("NameId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("RawLanguage")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("RawStatus")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<long>("SourceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int?>("StatusOverride")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorId");
|
||||||
|
|
||||||
|
b.HasIndex("DescriptionId");
|
||||||
|
|
||||||
|
b.HasIndex("NameId");
|
||||||
|
|
||||||
|
b.HasIndex("SourceId");
|
||||||
|
|
||||||
|
b.ToTable("Novels");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.NovelTag", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("DisplayNameId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long?>("NovelId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long?>("SourceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("TagType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DisplayNameId");
|
||||||
|
|
||||||
|
b.HasIndex("NovelId");
|
||||||
|
|
||||||
|
b.HasIndex("SourceId");
|
||||||
|
|
||||||
|
b.ToTable("Tags");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Person", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("ExternalUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Person");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Source", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Sources");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.TranslationEngine", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("TranslationEngines");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationText", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", null)
|
||||||
|
.WithMany("Texts")
|
||||||
|
.HasForeignKey("LocalizationKeyId");
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.TranslationEngine", "TranslationEngine")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("TranslationEngineId");
|
||||||
|
|
||||||
|
b.Navigation("TranslationEngine");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Chapter", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "Body")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("BodyId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "Name")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("NameId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Novel", null)
|
||||||
|
.WithMany("Chapters")
|
||||||
|
.HasForeignKey("NovelId");
|
||||||
|
|
||||||
|
b.Navigation("Body");
|
||||||
|
|
||||||
|
b.Navigation("Name");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Novel", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Person", "Author")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AuthorId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "Description")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DescriptionId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "Name")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("NameId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Source", "Source")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SourceId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Author");
|
||||||
|
|
||||||
|
b.Navigation("Description");
|
||||||
|
|
||||||
|
b.Navigation("Name");
|
||||||
|
|
||||||
|
b.Navigation("Source");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.NovelTag", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "DisplayName")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DisplayNameId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Novel", null)
|
||||||
|
.WithMany("Tags")
|
||||||
|
.HasForeignKey("NovelId");
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Source", "Source")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SourceId");
|
||||||
|
|
||||||
|
b.Navigation("DisplayName");
|
||||||
|
|
||||||
|
b.Navigation("Source");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Texts");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Novel", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Chapters");
|
||||||
|
|
||||||
|
b.Navigation("Tags");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddSourceKey : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Key",
|
||||||
|
table: "Sources",
|
||||||
|
type: "text",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Key",
|
||||||
|
table: "Sources");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
432
FictionArchive.Service.NovelService/Migrations/20251118030953_FixTagAssociation.Designer.cs
generated
Normal file
432
FictionArchive.Service.NovelService/Migrations/20251118030953_FixTagAssociation.Designer.cs
generated
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using FictionArchive.Service.NovelService.Services;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(NovelServiceDbContext))]
|
||||||
|
[Migration("20251118030953_FixTagAssociation")]
|
||||||
|
partial class FixTagAssociation
|
||||||
|
{
|
||||||
|
/// <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.NovelService.Models.Localization.LocalizationKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("LocalizationKey");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationText", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int>("Language")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<long?>("LocalizationKeyId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Text")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long?>("TranslationEngineId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("LocalizationKeyId");
|
||||||
|
|
||||||
|
b.HasIndex("TranslationEngineId");
|
||||||
|
|
||||||
|
b.ToTable("LocalizationText");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Chapter", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("BodyId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("NameId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long?>("NovelId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("Order")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("Revision")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BodyId");
|
||||||
|
|
||||||
|
b.HasIndex("NameId");
|
||||||
|
|
||||||
|
b.HasIndex("NovelId");
|
||||||
|
|
||||||
|
b.ToTable("Chapter");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Novel", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("DescriptionId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("ExternalId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long>("NameId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("RawLanguage")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("RawStatus")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<long>("SourceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int?>("StatusOverride")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorId");
|
||||||
|
|
||||||
|
b.HasIndex("DescriptionId");
|
||||||
|
|
||||||
|
b.HasIndex("NameId");
|
||||||
|
|
||||||
|
b.HasIndex("SourceId");
|
||||||
|
|
||||||
|
b.ToTable("Novels");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.NovelTag", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("DisplayNameId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long?>("SourceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("TagType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DisplayNameId");
|
||||||
|
|
||||||
|
b.HasIndex("SourceId");
|
||||||
|
|
||||||
|
b.ToTable("Tags");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Person", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("ExternalUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Person");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Source", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Sources");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.TranslationEngine", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("TranslationEngines");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NovelNovelTag", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("NovelsId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("TagsId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.HasKey("NovelsId", "TagsId");
|
||||||
|
|
||||||
|
b.HasIndex("TagsId");
|
||||||
|
|
||||||
|
b.ToTable("NovelNovelTag");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationText", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", null)
|
||||||
|
.WithMany("Texts")
|
||||||
|
.HasForeignKey("LocalizationKeyId");
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.TranslationEngine", "TranslationEngine")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("TranslationEngineId");
|
||||||
|
|
||||||
|
b.Navigation("TranslationEngine");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Chapter", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "Body")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("BodyId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "Name")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("NameId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Novel", null)
|
||||||
|
.WithMany("Chapters")
|
||||||
|
.HasForeignKey("NovelId");
|
||||||
|
|
||||||
|
b.Navigation("Body");
|
||||||
|
|
||||||
|
b.Navigation("Name");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Novel", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Person", "Author")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AuthorId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "Description")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DescriptionId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "Name")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("NameId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Source", "Source")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SourceId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Author");
|
||||||
|
|
||||||
|
b.Navigation("Description");
|
||||||
|
|
||||||
|
b.Navigation("Name");
|
||||||
|
|
||||||
|
b.Navigation("Source");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.NovelTag", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "DisplayName")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DisplayNameId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Source", "Source")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SourceId");
|
||||||
|
|
||||||
|
b.Navigation("DisplayName");
|
||||||
|
|
||||||
|
b.Navigation("Source");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NovelNovelTag", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Novel", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("NovelsId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.NovelTag", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("TagsId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Texts");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Novel", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Chapters");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class FixTagAssociation : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_Tags_Novels_NovelId",
|
||||||
|
table: "Tags");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_Tags_NovelId",
|
||||||
|
table: "Tags");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "NovelId",
|
||||||
|
table: "Tags");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "NovelNovelTag",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
NovelsId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
TagsId = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_NovelNovelTag", x => new { x.NovelsId, x.TagsId });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_NovelNovelTag_Novels_NovelsId",
|
||||||
|
column: x => x.NovelsId,
|
||||||
|
principalTable: "Novels",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_NovelNovelTag_Tags_TagsId",
|
||||||
|
column: x => x.TagsId,
|
||||||
|
principalTable: "Tags",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_NovelNovelTag_TagsId",
|
||||||
|
table: "NovelNovelTag",
|
||||||
|
column: "TagsId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "NovelNovelTag");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<long>(
|
||||||
|
name: "NovelId",
|
||||||
|
table: "Tags",
|
||||||
|
type: "bigint",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Tags_NovelId",
|
||||||
|
table: "Tags",
|
||||||
|
column: "NovelId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_Tags_Novels_NovelId",
|
||||||
|
table: "Tags",
|
||||||
|
column: "NovelId",
|
||||||
|
principalTable: "Novels",
|
||||||
|
principalColumn: "Id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,429 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using FictionArchive.Service.NovelService.Services;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(NovelServiceDbContext))]
|
||||||
|
partial class NovelServiceDbContextModelSnapshot : 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.NovelService.Models.Localization.LocalizationKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("LocalizationKey");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationText", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int>("Language")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<long?>("LocalizationKeyId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Text")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long?>("TranslationEngineId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("LocalizationKeyId");
|
||||||
|
|
||||||
|
b.HasIndex("TranslationEngineId");
|
||||||
|
|
||||||
|
b.ToTable("LocalizationText");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Chapter", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("BodyId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("NameId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long?>("NovelId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("Order")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("Revision")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BodyId");
|
||||||
|
|
||||||
|
b.HasIndex("NameId");
|
||||||
|
|
||||||
|
b.HasIndex("NovelId");
|
||||||
|
|
||||||
|
b.ToTable("Chapter");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Novel", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("AuthorId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("DescriptionId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("ExternalId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long>("NameId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("RawLanguage")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("RawStatus")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<long>("SourceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int?>("StatusOverride")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorId");
|
||||||
|
|
||||||
|
b.HasIndex("DescriptionId");
|
||||||
|
|
||||||
|
b.HasIndex("NameId");
|
||||||
|
|
||||||
|
b.HasIndex("SourceId");
|
||||||
|
|
||||||
|
b.ToTable("Novels");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.NovelTag", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("DisplayNameId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long?>("SourceId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<int>("TagType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DisplayNameId");
|
||||||
|
|
||||||
|
b.HasIndex("SourceId");
|
||||||
|
|
||||||
|
b.ToTable("Tags");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Person", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("ExternalUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Person");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Source", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Sources");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.TranslationEngine", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("TranslationEngines");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NovelNovelTag", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("NovelsId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<long>("TagsId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.HasKey("NovelsId", "TagsId");
|
||||||
|
|
||||||
|
b.HasIndex("TagsId");
|
||||||
|
|
||||||
|
b.ToTable("NovelNovelTag");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationText", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", null)
|
||||||
|
.WithMany("Texts")
|
||||||
|
.HasForeignKey("LocalizationKeyId");
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.TranslationEngine", "TranslationEngine")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("TranslationEngineId");
|
||||||
|
|
||||||
|
b.Navigation("TranslationEngine");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Chapter", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "Body")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("BodyId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "Name")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("NameId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Novel", null)
|
||||||
|
.WithMany("Chapters")
|
||||||
|
.HasForeignKey("NovelId");
|
||||||
|
|
||||||
|
b.Navigation("Body");
|
||||||
|
|
||||||
|
b.Navigation("Name");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Novel", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Person", "Author")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AuthorId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "Description")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DescriptionId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "Name")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("NameId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Source", "Source")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SourceId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Author");
|
||||||
|
|
||||||
|
b.Navigation("Description");
|
||||||
|
|
||||||
|
b.Navigation("Name");
|
||||||
|
|
||||||
|
b.Navigation("Source");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.NovelTag", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", "DisplayName")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DisplayNameId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Source", "Source")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SourceId");
|
||||||
|
|
||||||
|
b.Navigation("DisplayName");
|
||||||
|
|
||||||
|
b.Navigation("Source");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NovelNovelTag", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.Novel", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("NovelsId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("FictionArchive.Service.NovelService.Models.Novels.NovelTag", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("TagsId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Localization.LocalizationKey", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Texts");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("FictionArchive.Service.NovelService.Models.Novels.Novel", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Chapters");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace FictionArchive.Service.NovelService.Models.Enums;
|
||||||
|
|
||||||
|
public enum NovelStatus
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
InProgress,
|
||||||
|
Completed,
|
||||||
|
Hiatus,
|
||||||
|
Abandoned
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace FictionArchive.Service.NovelService.Models.Enums;
|
||||||
|
|
||||||
|
public enum TagType
|
||||||
|
{
|
||||||
|
System,
|
||||||
|
External,
|
||||||
|
UserDefined,
|
||||||
|
Genre,
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using FictionArchive.Common.Enums;
|
||||||
|
using FictionArchive.Service.NovelService.Models.Novels;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Models.Localization;
|
||||||
|
|
||||||
|
public class LocalizationKey : BaseEntity<uint>
|
||||||
|
{
|
||||||
|
public List<LocalizationText> Texts { get; set; }
|
||||||
|
|
||||||
|
public static LocalizationKey CreateFromText(string text, Language language)
|
||||||
|
{
|
||||||
|
return new LocalizationKey()
|
||||||
|
{
|
||||||
|
Texts = new List<LocalizationText>()
|
||||||
|
{
|
||||||
|
new LocalizationText()
|
||||||
|
{
|
||||||
|
Language = language,
|
||||||
|
Text = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using FictionArchive.Common.Enums;
|
||||||
|
using FictionArchive.Service.NovelService.Models.Novels;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Models.Localization;
|
||||||
|
|
||||||
|
public class LocalizationText : BaseEntity<uint>
|
||||||
|
{
|
||||||
|
public Language Language { get; set; }
|
||||||
|
public string Text { get; set; }
|
||||||
|
public TranslationEngine? TranslationEngine { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace FictionArchive.Service.NovelService.Models.Novels;
|
||||||
|
|
||||||
|
public abstract class BaseEntity<TKey>
|
||||||
|
{
|
||||||
|
public uint Id { get; set; }
|
||||||
|
public DateTime CreatedUtc { get; set; }
|
||||||
|
public DateTime UpdatedUtc { get; set; }
|
||||||
|
}
|
||||||
13
FictionArchive.Service.NovelService/Models/Novels/Chapter.cs
Normal file
13
FictionArchive.Service.NovelService/Models/Novels/Chapter.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using FictionArchive.Service.NovelService.Models.Localization;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Models.Novels;
|
||||||
|
|
||||||
|
public class Chapter : BaseEntity<uint>
|
||||||
|
{
|
||||||
|
public uint Revision { get; set; }
|
||||||
|
public uint Order { get; set; }
|
||||||
|
public string? Url { get; set; }
|
||||||
|
|
||||||
|
public LocalizationKey Name { get; set; }
|
||||||
|
public LocalizationKey Body { get; set; }
|
||||||
|
}
|
||||||
24
FictionArchive.Service.NovelService/Models/Novels/Novel.cs
Normal file
24
FictionArchive.Service.NovelService/Models/Novels/Novel.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using FictionArchive.Common.Enums;
|
||||||
|
using FictionArchive.Service.NovelService.Models.Localization;
|
||||||
|
using NovelStatus = FictionArchive.Service.NovelService.Models.Enums.NovelStatus;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Models.Novels;
|
||||||
|
|
||||||
|
public class Novel : BaseEntity<uint>
|
||||||
|
{
|
||||||
|
public Person Author { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
public Language RawLanguage { get; set; }
|
||||||
|
|
||||||
|
public NovelStatus RawStatus { get; set; }
|
||||||
|
public NovelStatus? StatusOverride { get; set; }
|
||||||
|
|
||||||
|
public Source Source { get; set; }
|
||||||
|
public string ExternalId { get; set; }
|
||||||
|
|
||||||
|
public LocalizationKey Name { get; set; }
|
||||||
|
public LocalizationKey Description { get; set; }
|
||||||
|
|
||||||
|
public List<Chapter> Chapters { get; set; }
|
||||||
|
public List<NovelTag> Tags { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using FictionArchive.Service.NovelService.Models.Enums;
|
||||||
|
using FictionArchive.Service.NovelService.Models.Localization;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Models.Novels;
|
||||||
|
|
||||||
|
public class NovelTag : BaseEntity<uint>
|
||||||
|
{
|
||||||
|
public string Key { get; set; }
|
||||||
|
public LocalizationKey DisplayName { get; set; }
|
||||||
|
public TagType TagType { get; set; }
|
||||||
|
|
||||||
|
public Source? Source { get; set; }
|
||||||
|
public List<Novel> Novels { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace FictionArchive.Service.NovelService.Models.Novels;
|
||||||
|
|
||||||
|
public class Person : BaseEntity<uint>
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string? ExternalUrl { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace FictionArchive.Service.NovelService.Models.Novels;
|
||||||
|
|
||||||
|
public class Source : BaseEntity<uint>
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Key { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using FictionArchive.Service.NovelService.Models.Novels;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Models;
|
||||||
|
|
||||||
|
public class SourceConfiguration : BaseEntity<uint>
|
||||||
|
{
|
||||||
|
public string Key { get; set; }
|
||||||
|
|
||||||
|
[Column(TypeName = "jsonb")]
|
||||||
|
public string Configuration { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace FictionArchive.Service.NovelService.Models.Novels;
|
||||||
|
|
||||||
|
public class TranslationEngine : BaseEntity<uint>
|
||||||
|
{
|
||||||
|
public string Key { get; set; }
|
||||||
|
public string DisplayName { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace FictionArchive.Service.NovelService.Models.SourceAdapters;
|
||||||
|
|
||||||
|
public class ChapterMetadata
|
||||||
|
{
|
||||||
|
public uint Revision { get; set; }
|
||||||
|
public uint Order { get; set; }
|
||||||
|
public string? Url { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using FictionArchive.Common.Enums;
|
||||||
|
using FictionArchive.Service.NovelService.Models.Enums;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Models.SourceAdapters;
|
||||||
|
|
||||||
|
public class NovelMetadata
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string AuthorName { get; set; }
|
||||||
|
public string AuthorUrl { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
public string ExternalId { get; set; }
|
||||||
|
|
||||||
|
public Language RawLanguage { get; set; }
|
||||||
|
public NovelStatus RawStatus { get; set; }
|
||||||
|
|
||||||
|
public List<ChapterMetadata> Chapters { get; set; }
|
||||||
|
public List<string> SourceTags { get; set; }
|
||||||
|
public List<string> SystemTags { get; set; }
|
||||||
|
public SourceDescriptor SourceDescriptor { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace FictionArchive.Service.NovelService.Models.SourceAdapters;
|
||||||
|
|
||||||
|
public class SourceDescriptor
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Key { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
}
|
||||||
74
FictionArchive.Service.NovelService/Program.cs
Normal file
74
FictionArchive.Service.NovelService/Program.cs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
using FictionArchive.Service.NovelService.GraphQL;
|
||||||
|
using FictionArchive.Service.NovelService.Services;
|
||||||
|
using FictionArchive.Service.NovelService.Services.SourceAdapters;
|
||||||
|
using FictionArchive.Service.NovelService.Services.SourceAdapters.Novelpia;
|
||||||
|
using FictionArchive.Service.Shared.Services.GraphQL;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService;
|
||||||
|
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Services.AddMemoryCache();
|
||||||
|
|
||||||
|
#region GraphQL
|
||||||
|
|
||||||
|
builder.Services.AddGraphQLServer()
|
||||||
|
.AddQueryType<Query>()
|
||||||
|
.AddMutationType<Mutation>()
|
||||||
|
.AddType<UnsignedIntType>()
|
||||||
|
.AddMutationConventions(applyToAllMutations: true)
|
||||||
|
.AddFiltering(opt => opt.AddDefaults().BindRuntimeType<uint, UnsignedIntOperationFilterInputType>())
|
||||||
|
.AddSorting()
|
||||||
|
.AddProjections();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Database
|
||||||
|
|
||||||
|
builder.Services.AddDbContext<NovelServiceDbContext>(opt =>
|
||||||
|
{
|
||||||
|
opt.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"));
|
||||||
|
});
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Source Adapters
|
||||||
|
|
||||||
|
builder.Services.Configure<NovelpiaConfiguration>(builder.Configuration.GetSection("Novelpia"));
|
||||||
|
builder.Services.AddHttpClient<NovelpiaAuthMessageHandler>(client =>
|
||||||
|
{
|
||||||
|
client.BaseAddress = new Uri("https://novelpia.com");
|
||||||
|
});
|
||||||
|
builder.Services.AddHttpClient<ISourceAdapter, NovelpiaAdapter>(client =>
|
||||||
|
{
|
||||||
|
client.BaseAddress = new Uri("https://novelpia.com");
|
||||||
|
})
|
||||||
|
.AddHttpMessageHandler<NovelpiaAuthMessageHandler>();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
builder.Services.AddHealthChecks();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
// Update database
|
||||||
|
using (var scope = app.Services.CreateScope())
|
||||||
|
{
|
||||||
|
var dbContext = scope.ServiceProvider.GetRequiredService<NovelServiceDbContext>();
|
||||||
|
dbContext.UpdateDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.MapHealthChecks("/healthz");
|
||||||
|
|
||||||
|
app.MapGraphQL();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:32130",
|
||||||
|
"sslPort": 44387
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"applicationUrl": "http://localhost:5101",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "graphql",
|
||||||
|
"applicationUrl": "https://localhost:7208;http://localhost:5101",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using FictionArchive.Service.NovelService.Models.Novels;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Services;
|
||||||
|
|
||||||
|
public class NovelServiceDbContext(DbContextOptions options, ILogger<NovelServiceDbContext> logger)
|
||||||
|
: DbContext(options)
|
||||||
|
{
|
||||||
|
public DbSet<Novel> Novels { get; set; }
|
||||||
|
public DbSet<Source> Sources { get; set; }
|
||||||
|
public DbSet<TranslationEngine> TranslationEngines { get; set; }
|
||||||
|
public DbSet<NovelTag> Tags { get; set; }
|
||||||
|
|
||||||
|
private readonly ILogger _logger = logger;
|
||||||
|
|
||||||
|
public void UpdateDatabase()
|
||||||
|
{
|
||||||
|
IEnumerable<string> pendingMigrations = Database.GetPendingMigrations();
|
||||||
|
if (!pendingMigrations.Any())
|
||||||
|
{
|
||||||
|
_logger.LogDebug("No pending migrations found, continuing.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string migration in pendingMigrations)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Found pending migration with name {migrationName}.", migration);
|
||||||
|
}
|
||||||
|
_logger.LogInformation("Attempting to apply pending migrations...");
|
||||||
|
Database.Migrate();
|
||||||
|
_logger.LogInformation("Migrations applied.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using FictionArchive.Service.NovelService.Models.Novels;
|
||||||
|
using FictionArchive.Service.NovelService.Models.SourceAdapters;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Services.SourceAdapters;
|
||||||
|
|
||||||
|
public interface ISourceAdapter
|
||||||
|
{
|
||||||
|
public SourceDescriptor SourceDescriptor { get; }
|
||||||
|
public Task<bool> CanProcessNovel(string url);
|
||||||
|
public Task<NovelMetadata> GetMetadata(string novelUrl);
|
||||||
|
public Task<string> GetRawChapter(string chapterUrl);
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using FictionArchive.Common.Enums;
|
||||||
|
using FictionArchive.Service.NovelService.Constants;
|
||||||
|
using FictionArchive.Service.NovelService.Models.Enums;
|
||||||
|
using FictionArchive.Service.NovelService.Models.SourceAdapters;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Services.SourceAdapters.Novelpia;
|
||||||
|
|
||||||
|
public class NovelpiaAdapter : ISourceAdapter
|
||||||
|
{
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
private const string NovelIdRegex = @"novelpia.com\/novel\/(\d+)";
|
||||||
|
private const string ChapterIdRegex = @"novelpia.com\/viewer\/(\d+)";
|
||||||
|
private const string EpisodeListEndpoint = "/proc/episode_list";
|
||||||
|
private const string ChapterDownloadEndpoint = "/proc/viewer_data/";
|
||||||
|
|
||||||
|
private const string SourceKey = "novelpia";
|
||||||
|
private const string SourceName = "Novelpia";
|
||||||
|
private const string SourceUrl = "https://novelpia.com";
|
||||||
|
|
||||||
|
private const string ChapterDownloadFailedMessage = "본인인증";
|
||||||
|
|
||||||
|
public NovelpiaAdapter(HttpClient httpClient, ILogger<NovelpiaAdapter> logger)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SourceDescriptor SourceDescriptor
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new SourceDescriptor()
|
||||||
|
{
|
||||||
|
Name = SourceName,
|
||||||
|
Key = SourceKey,
|
||||||
|
Url = SourceUrl
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> CanProcessNovel(string url)
|
||||||
|
{
|
||||||
|
return Regex.IsMatch(url, @"https://novelpia.com/novel/(\d+)");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<NovelMetadata> GetMetadata(string novelUrl)
|
||||||
|
{
|
||||||
|
// PROCESS
|
||||||
|
// Get novelurl
|
||||||
|
// Title is <div class="ep-info-line epnew-novel-title">따먹히는 순애 금태양</div>
|
||||||
|
// Author is <a class="writer-name" href="/user/579482">구다수 </a>
|
||||||
|
// Chapters are gotten from the episode_list proc
|
||||||
|
|
||||||
|
uint novelId = uint.Parse(Regex.Match(novelUrl, NovelIdRegex).Groups[1].Value);
|
||||||
|
|
||||||
|
NovelMetadata novel = new NovelMetadata()
|
||||||
|
{
|
||||||
|
Url = novelUrl,
|
||||||
|
RawLanguage = Language.Kr,
|
||||||
|
ExternalId = novelId.ToString(),
|
||||||
|
SystemTags = new List<string>(),
|
||||||
|
SourceTags = new List<string>(),
|
||||||
|
Chapters = new List<ChapterMetadata>(),
|
||||||
|
SourceDescriptor = SourceDescriptor
|
||||||
|
};
|
||||||
|
|
||||||
|
// Novel metadata
|
||||||
|
var novelData = await _httpClient.GetStringAsync(novelUrl);
|
||||||
|
var novelNameMatch = Regex.Match(novelData, @"<div class=""ep-info-line epnew-novel-title"">(.+)<\/div>");
|
||||||
|
var authorMatch = Regex.Match(novelData, @"(?s)<a\s+class=""writer-name""\s+href=""([^""]+)"">\s*(.*?)\s*<\/a>");
|
||||||
|
var descriptionMatch = Regex.Match(novelData, @"(?s)<div\s+class=""synopsis"">\s*(.*?)\s*<\/div>");
|
||||||
|
|
||||||
|
novel.Name = novelNameMatch.Groups[1].Value;
|
||||||
|
novel.Description = descriptionMatch.Groups[1].Value;
|
||||||
|
novel.AuthorName = authorMatch.Groups[2].Value;
|
||||||
|
novel.AuthorUrl = authorMatch.Groups[2].Value;
|
||||||
|
|
||||||
|
// Some badge info
|
||||||
|
var badgeSet = Regex.Match(novelData, @"(?s)<p\s+class=""in-badge"">(.*?)<\/p>");
|
||||||
|
var badgeMatches = Regex.Matches(badgeSet.Groups[1].Value, @"<span[^>]*>(.*?)<\/span>");
|
||||||
|
foreach (Match badge in badgeMatches)
|
||||||
|
{
|
||||||
|
var innerText = badge.Groups[1].Value;
|
||||||
|
if (innerText == "19")
|
||||||
|
{
|
||||||
|
novel.SystemTags.Add(SystemTags.Nsfw);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (innerText == "완결")
|
||||||
|
{
|
||||||
|
novel.RawStatus = NovelStatus.Completed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
novel.RawStatus = NovelStatus.InProgress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Novel tags
|
||||||
|
HashSet<string> tags = new HashSet<string>();
|
||||||
|
var tagSetMatch = Regex.Match(novelData, @"(?s)<p\s+class=""writer-tag"">(.*?)<\/p>");
|
||||||
|
var tagMatches =
|
||||||
|
Regex.Matches(tagSetMatch.Groups[1].Value, @"<span[^>]*>#(.*?)<\/span>");
|
||||||
|
foreach (Match tagMatch in tagMatches)
|
||||||
|
{
|
||||||
|
var tagText = tagMatch.Groups[1].Value;
|
||||||
|
tags.Add(tagText);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string tag in tags)
|
||||||
|
{
|
||||||
|
novel.SourceTags.Add(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chapters
|
||||||
|
uint page = 0;
|
||||||
|
List<ChapterMetadata> chapters = new List<ChapterMetadata>();
|
||||||
|
List<uint> seenChapterIds = new List<uint>();
|
||||||
|
uint chapterOrder = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
await Task.Delay(500);
|
||||||
|
_logger.LogInformation("Next chapter batch");
|
||||||
|
var response = await _httpClient.PostAsync(EpisodeListEndpoint, new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{"novel_no", novelId.ToString()},
|
||||||
|
{"sort", "DOWN"},
|
||||||
|
{"page", page.ToString()}
|
||||||
|
}));
|
||||||
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
|
var capturedChapters = Regex.Matches(responseContent, @"id=""bookmark_(\d+)""></i>(.+?)</b>");
|
||||||
|
if (seenChapterIds.Contains(uint.Parse(capturedChapters[0].Groups[1].Value)))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
foreach (Match chapter in capturedChapters)
|
||||||
|
{
|
||||||
|
string chapterId = chapter.Groups[1].Value;
|
||||||
|
string chapterName = chapter.Groups[2].Value;
|
||||||
|
chapters.Add(new ChapterMetadata
|
||||||
|
{
|
||||||
|
Revision = 0,
|
||||||
|
Order = chapterOrder,
|
||||||
|
Url = $"https://novelpia.com/viewer/{chapterId}",
|
||||||
|
Name = chapterName
|
||||||
|
});
|
||||||
|
seenChapterIds.Add(uint.Parse(chapterId));
|
||||||
|
chapterOrder++;
|
||||||
|
}
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
novel.Chapters = chapters;
|
||||||
|
|
||||||
|
return novel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetRawChapter(string chapterUrl)
|
||||||
|
{
|
||||||
|
var chapterId = uint.Parse(Regex.Match(chapterUrl, ChapterIdRegex).Groups[1].Value);
|
||||||
|
var endpoint = ChapterDownloadEndpoint + chapterId;
|
||||||
|
var result = await _httpClient.PostAsync(endpoint, null);
|
||||||
|
var responseContent = await result.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(responseContent) || responseContent.Contains(ChapterDownloadFailedMessage))
|
||||||
|
{
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
using var doc = JsonDocument.Parse(responseContent);
|
||||||
|
JsonElement root = doc.RootElement;
|
||||||
|
|
||||||
|
// Get the "s" array
|
||||||
|
JsonElement sArray = root.GetProperty("s");
|
||||||
|
|
||||||
|
foreach (JsonElement item in sArray.EnumerateArray())
|
||||||
|
{
|
||||||
|
string text = item.GetProperty("text").GetString();
|
||||||
|
if (text.Contains("cover-wrapper"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (text.Contains("opacity: 0"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append(WebUtility.HtmlDecode(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.NovelService.Services.SourceAdapters.Novelpia;
|
||||||
|
|
||||||
|
public class NovelpiaAuthMessageHandler : DelegatingHandler
|
||||||
|
{
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly IMemoryCache _cache;
|
||||||
|
private readonly NovelpiaConfiguration _configuration;
|
||||||
|
private const string CacheKey = "novelpia_loginkey";
|
||||||
|
private const string LoginUrl = "/proc/login";
|
||||||
|
private const string LoginSuccessMessage = "감사합니다";
|
||||||
|
private const string UserAgent =
|
||||||
|
"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36";
|
||||||
|
|
||||||
|
public NovelpiaAuthMessageHandler(HttpClient httpClient, IOptions<NovelpiaConfiguration> configuration, IMemoryCache cache)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_configuration = configuration.Value;
|
||||||
|
_cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
string loginKey = await GetLoginKey();
|
||||||
|
request.Headers.Add("cookie", $"LOGINKEY={loginKey}");
|
||||||
|
request.Headers.UserAgent.ParseAdd(UserAgent);
|
||||||
|
return await base.SendAsync(request, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> GetLoginKey()
|
||||||
|
{
|
||||||
|
if (!_cache.TryGetValue(CacheKey, out string? loginKey))
|
||||||
|
{
|
||||||
|
var random = new Random();
|
||||||
|
var characters = "0123456789abcdef";
|
||||||
|
var firstPart = new string(Enumerable.Range(0, 32).Select(_ => characters[random.Next(characters.Length)]).ToArray());
|
||||||
|
var secondPart = new string(Enumerable.Range(0, 32).Select(_ => characters[random.Next(characters.Length)]).ToArray());
|
||||||
|
loginKey = firstPart + "_" + secondPart;
|
||||||
|
|
||||||
|
HttpRequestMessage loginMessage = new HttpRequestMessage(HttpMethod.Post, LoginUrl);
|
||||||
|
loginMessage.Headers.Add("cookie", $"LOGINKEY={loginKey}");
|
||||||
|
loginMessage.Headers.UserAgent.ParseAdd(UserAgent);
|
||||||
|
loginMessage.Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "redirecturl", string.Empty },
|
||||||
|
{ "email", _configuration.Username },
|
||||||
|
{ "wd", _configuration.Password }
|
||||||
|
});
|
||||||
|
|
||||||
|
var response = await _httpClient.SendAsync(loginMessage);
|
||||||
|
using (var streamReader = new StreamReader(response.Content.ReadAsStream()))
|
||||||
|
{
|
||||||
|
if (streamReader.ReadToEnd().Contains(LoginSuccessMessage))
|
||||||
|
{
|
||||||
|
_cache.Set(CacheKey, loginKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loginKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace FictionArchive.Service.NovelService.Services.SourceAdapters.Novelpia;
|
||||||
|
|
||||||
|
public class NovelpiaConfiguration
|
||||||
|
{
|
||||||
|
public string Username { get; set; }
|
||||||
|
public string Password { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
FictionArchive.Service.NovelService/appsettings.json
Normal file
16
FictionArchive.Service.NovelService/appsettings.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Novelpia": {
|
||||||
|
"Username": "REPLACE_ME",
|
||||||
|
"Password": "REPLACE_ME"
|
||||||
|
},
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "Host=localhost;Database=FictionArchive_NovelService;Username=postgres;password=postgres"
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="HotChocolate.AspNetCore" Version="15.1.11" />
|
||||||
|
<PackageReference Include="HotChocolate.Data.EntityFramework" Version="15.1.11" />
|
||||||
|
<PackageReference Include="HotChocolate.Types.Scalars" Version="15.1.11" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.11" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.11">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.Shared.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstract DbContext handling boilerplate shared between our contexts. Should not share actual data.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class FictionArchiveDbContext : DbContext
|
||||||
|
{
|
||||||
|
protected readonly ILogger _logger;
|
||||||
|
|
||||||
|
protected FictionArchiveDbContext(DbContextOptions options, ILogger logger) : base(options)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using HotChocolate.Data.Filters;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.Shared.Services.GraphQL;
|
||||||
|
|
||||||
|
public class UnsignedIntOperationFilterInputType
|
||||||
|
: ComparableOperationFilterInputType<UnsignedIntType>
|
||||||
|
{
|
||||||
|
protected override void Configure(IFilterInputTypeDescriptor descriptor)
|
||||||
|
{
|
||||||
|
descriptor.Name("UnsignedIntOperationFilterInputType");
|
||||||
|
base.Configure(descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
FictionArchive.Service.TranslationService/Dockerfile
Normal file
23
FictionArchive.Service.TranslationService/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||||
|
USER $APP_UID
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 8080
|
||||||
|
EXPOSE 8081
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["FictionArchive.Service.TranslationService/FictionArchive.Service.TranslationService.csproj", "FictionArchive.Service.TranslationService/"]
|
||||||
|
RUN dotnet restore "FictionArchive.Service.TranslationService/FictionArchive.Service.TranslationService.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/FictionArchive.Service.TranslationService"
|
||||||
|
RUN dotnet build "./FictionArchive.Service.TranslationService.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
RUN dotnet publish "./FictionArchive.Service.TranslationService.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "FictionArchive.Service.TranslationService.dll"]
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="HotChocolate.AspNetCore" Version="15.1.11" />
|
||||||
|
<PackageReference Include="HotChocolate.Data.EntityFramework" Version="15.1.11" />
|
||||||
|
<PackageReference Include="HotChocolate.Types.Scalars" Version="15.1.11" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.11" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.11">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
|
<PackageReference Include="DeepL.net" Version="1.17.0" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="..\.dockerignore">
|
||||||
|
<Link>.dockerignore</Link>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\FictionArchive.Common\FictionArchive.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\FictionArchive.Service.Shared\FictionArchive.Service.Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using FictionArchive.Common.Enums;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
var engine = translationEngines.FirstOrDefault(engine => engine.Descriptor.Key == translationEngineKey);
|
||||||
|
var translation = await engine.GetTranslation(text, from, to);
|
||||||
|
return translation;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
FictionArchive.Service.TranslationService/GraphQL/Query.cs
Normal file
14
FictionArchive.Service.TranslationService/GraphQL/Query.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using FictionArchive.Service.TranslationService.Models;
|
||||||
|
using FictionArchive.Service.TranslationService.Services.TranslationEngines;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.TranslationService.GraphQL;
|
||||||
|
|
||||||
|
public class Query
|
||||||
|
{
|
||||||
|
[UseFiltering]
|
||||||
|
[UseSorting]
|
||||||
|
public IEnumerable<TranslationEngineDescriptor> GetTranslationEngines(IEnumerable<ITranslationEngineAdapter> engines)
|
||||||
|
{
|
||||||
|
return engines.Select(engine => engine.Descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace FictionArchive.Service.TranslationService.Models;
|
||||||
|
|
||||||
|
public class TranslationEngineDescriptor
|
||||||
|
{
|
||||||
|
public string DisplayName { get; set; }
|
||||||
|
public string Key { get; set; }
|
||||||
|
}
|
||||||
50
FictionArchive.Service.TranslationService/Program.cs
Normal file
50
FictionArchive.Service.TranslationService/Program.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
using DeepL;
|
||||||
|
using FictionArchive.Service.Shared.Services.GraphQL;
|
||||||
|
using FictionArchive.Service.TranslationService.GraphQL;
|
||||||
|
using FictionArchive.Service.TranslationService.Services.TranslationEngines;
|
||||||
|
using FictionArchive.Service.TranslationService.Services.TranslationEngines.DeepLTranslate;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.TranslationService;
|
||||||
|
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Services.AddHealthChecks();
|
||||||
|
|
||||||
|
#region GraphQL
|
||||||
|
|
||||||
|
builder.Services.AddGraphQLServer()
|
||||||
|
.AddQueryType<Query>()
|
||||||
|
.AddMutationType<Mutation>()
|
||||||
|
.AddType<UnsignedIntType>()
|
||||||
|
.AddMutationConventions(applyToAllMutations: true)
|
||||||
|
.AddFiltering(opt => opt.AddDefaults().BindRuntimeType<uint, UnsignedIntOperationFilterInputType>())
|
||||||
|
.AddSorting()
|
||||||
|
.AddProjections();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Translation Adapter
|
||||||
|
|
||||||
|
builder.Services.AddTransient<DeepLClient>(provider =>
|
||||||
|
{
|
||||||
|
return new DeepLClient(builder.Configuration["DeepL:ApiKey"]);
|
||||||
|
});
|
||||||
|
builder.Services.AddTransient<ITranslationEngineAdapter, DeepLTranslationAdapater>();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.MapHealthChecks("/healthz");
|
||||||
|
|
||||||
|
app.MapGraphQL();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:36751",
|
||||||
|
"sslPort": 44335
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"applicationUrl": "http://localhost:5134",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "graphql",
|
||||||
|
"applicationUrl": "https://localhost:7275;http://localhost:5134",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using DeepL;
|
||||||
|
using DeepL.Model;
|
||||||
|
using FictionArchive.Service.TranslationService.Models;
|
||||||
|
using Language = FictionArchive.Common.Enums.Language;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.TranslationService.Services.TranslationEngines.DeepLTranslate;
|
||||||
|
|
||||||
|
public class DeepLTranslationAdapater : ITranslationEngineAdapter
|
||||||
|
{
|
||||||
|
private readonly DeepLClient _deepLClient;
|
||||||
|
private readonly ILogger<DeepLTranslationAdapater> _logger;
|
||||||
|
|
||||||
|
private const string DisplayName = "DeepL";
|
||||||
|
private const string Key = "deepl";
|
||||||
|
|
||||||
|
public DeepLTranslationAdapater(DeepLClient deepLClient, ILogger<DeepLTranslationAdapater> logger)
|
||||||
|
{
|
||||||
|
_deepLClient = deepLClient;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TranslationEngineDescriptor Descriptor
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new TranslationEngineDescriptor()
|
||||||
|
{
|
||||||
|
DisplayName = DisplayName,
|
||||||
|
Key = Key,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string?> GetTranslation(string body, Language from, Language to)
|
||||||
|
{
|
||||||
|
TextResult translationResult = await _deepLClient.TranslateTextAsync(body, GetLanguageCode(from), GetLanguageCode(to));
|
||||||
|
_logger.LogInformation("Translated text. Usage statistics: CHARACTERS BILLED {TranslationResultBilledCharacters}", translationResult.BilledCharacters);
|
||||||
|
return translationResult.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetLanguageCode(Language language)
|
||||||
|
{
|
||||||
|
return language switch
|
||||||
|
{
|
||||||
|
Language.En => LanguageCode.EnglishAmerican,
|
||||||
|
Language.Kr => LanguageCode.Korean,
|
||||||
|
Language.Ch => LanguageCode.Chinese,
|
||||||
|
Language.Ja => LanguageCode.Japanese
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using FictionArchive.Common.Enums;
|
||||||
|
using FictionArchive.Service.TranslationService.Models;
|
||||||
|
|
||||||
|
namespace FictionArchive.Service.TranslationService.Services.TranslationEngines;
|
||||||
|
|
||||||
|
public interface ITranslationEngineAdapter
|
||||||
|
{
|
||||||
|
public TranslationEngineDescriptor Descriptor { get; }
|
||||||
|
public Task<string?> GetTranslation(string body, Language from, Language to);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
FictionArchive.Service.TranslationService/appsettings.json
Normal file
12
FictionArchive.Service.TranslationService/appsettings.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DeepL": {
|
||||||
|
"ApiKey": "REPLACE_ME"
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
40
FictionArchive.sln
Normal file
40
FictionArchive.sln
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FictionArchive.Common", "FictionArchive.Common\FictionArchive.Common.csproj", "{ABF1BA10-9E76-45BE-9947-E20445A68147}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FictionArchive.API", "FictionArchive.API\FictionArchive.API.csproj", "{420CC1A1-9DBC-40EC-B9E3-D4B25D71B9A9}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FictionArchive.Service.NovelService", "FictionArchive.Service.NovelService\FictionArchive.Service.NovelService.csproj", "{546231B6-CE6C-4600-A089-A25FE0F61006}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FictionArchive.Service.TranslationService", "FictionArchive.Service.TranslationService\FictionArchive.Service.TranslationService.csproj", "{BE858DD7-C2A8-44D7-B4DB-9668E5BC9A26}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FictionArchive.Service.Shared", "FictionArchive.Service.Shared\FictionArchive.Service.Shared.csproj", "{82638874-304C-43E6-8EFA-8AD4C41C4435}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{ABF1BA10-9E76-45BE-9947-E20445A68147}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{ABF1BA10-9E76-45BE-9947-E20445A68147}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{ABF1BA10-9E76-45BE-9947-E20445A68147}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{ABF1BA10-9E76-45BE-9947-E20445A68147}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{420CC1A1-9DBC-40EC-B9E3-D4B25D71B9A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{420CC1A1-9DBC-40EC-B9E3-D4B25D71B9A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{420CC1A1-9DBC-40EC-B9E3-D4B25D71B9A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{420CC1A1-9DBC-40EC-B9E3-D4B25D71B9A9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{546231B6-CE6C-4600-A089-A25FE0F61006}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{546231B6-CE6C-4600-A089-A25FE0F61006}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{546231B6-CE6C-4600-A089-A25FE0F61006}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{546231B6-CE6C-4600-A089-A25FE0F61006}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{BE858DD7-C2A8-44D7-B4DB-9668E5BC9A26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{BE858DD7-C2A8-44D7-B4DB-9668E5BC9A26}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{BE858DD7-C2A8-44D7-B4DB-9668E5BC9A26}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{BE858DD7-C2A8-44D7-B4DB-9668E5BC9A26}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{82638874-304C-43E6-8EFA-8AD4C41C4435}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{82638874-304C-43E6-8EFA-8AD4C41C4435}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{82638874-304C-43E6-8EFA-8AD4C41C4435}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{82638874-304C-43E6-8EFA-8AD4C41C4435}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
Reference in New Issue
Block a user