Compare commits
5 Commits
6a83de1f5d
...
6b5c5d0d3b
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b5c5d0d3b | |||
| adbc3a28d6 | |||
| f71283d617 | |||
| ca78d6ace4 | |||
| 400faa2fc3 |
@@ -0,0 +1,171 @@
|
|||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using OakArchive.Entities.CardMarket;
|
||||||
|
using OakArchive.Entities.Cards;
|
||||||
|
using OakArchive.Entities.Collection;
|
||||||
|
using OakArchive.Entities.Grading;
|
||||||
|
using OakArchive.Entities.Serie;
|
||||||
|
using OakArchive.Entities.Set;
|
||||||
|
using OakArchive.Entities.TCGdex;
|
||||||
|
using OakArchive.Entities.User;
|
||||||
|
|
||||||
|
namespace OakArchive.Database;
|
||||||
|
|
||||||
|
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid>, Guid>
|
||||||
|
{
|
||||||
|
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DbSet<Settings> Settings { get; set; } = null!;
|
||||||
|
public DbSet<Collection> Collections { get; set; } = null!;
|
||||||
|
public DbSet<CollectedCard> CollectedCards { get; set; } = null!;
|
||||||
|
public DbSet<Grading> Gradings { get; set; } = null!;
|
||||||
|
public DbSet<TcgDexCardInfo> TcgDexCardInfos { get; set; } = null!;
|
||||||
|
public DbSet<TcgDexSetInfo> TcgDexSetInfos { get; set; } = null!;
|
||||||
|
public DbSet<TcgDexSeriesInfo> TcgDexSeriesInfos { get; set; } = null!;
|
||||||
|
public DbSet<Serie> Series { get; set; } = null!;
|
||||||
|
public DbSet<Set> Sets { get; set; } = null!;
|
||||||
|
public DbSet<Legal> Legals { get; set; } = null!;
|
||||||
|
public DbSet<CardCount> CardCounts { get; set; } = null!;
|
||||||
|
public DbSet<Card> Cards { get; set; } = null!;
|
||||||
|
public DbSet<Attack> Attacks { get; set; } = null!;
|
||||||
|
public DbSet<Weakness> Weaknesses { get; set; } = null!;
|
||||||
|
public DbSet<CardMarketSetInfo> CardMarketSetInfos { get; set; } = null!;
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
// 1. Basis-Mapping für ASP.NET Core Identity (Pflicht!)
|
||||||
|
base.OnModelCreating(modelBuilder);
|
||||||
|
|
||||||
|
#region User & Settings
|
||||||
|
|
||||||
|
modelBuilder.Entity<ApplicationUser>(entity =>
|
||||||
|
{
|
||||||
|
// Konfiguration für das private Feld 'DefaultCollection' als Backing Field
|
||||||
|
entity.Property<Guid>("DefaultCollectionId");
|
||||||
|
|
||||||
|
entity.HasOne<Collection>()
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DefaultCollectionId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
// n:m Beziehung zwischen Usern und Collections (Members)
|
||||||
|
entity.HasMany(u => u.Collections)
|
||||||
|
.WithMany(c => c.Members)
|
||||||
|
.UsingEntity(j => j.ToTable("UserCollections"));
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Settings>()
|
||||||
|
.HasOne(s => s.User)
|
||||||
|
.WithOne(u => u.Settings)
|
||||||
|
.HasForeignKey<Settings>(s => s.UserId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 1:1 TCGdex & CardMarket Mapping
|
||||||
|
|
||||||
|
// EF Core mitgeteilt, auf welcher Seite der FK physisch liegt
|
||||||
|
modelBuilder.Entity<Card>()
|
||||||
|
.HasOne(c => c.TcgDexCardInfo)
|
||||||
|
.WithOne(t => t.Card)
|
||||||
|
.HasForeignKey<TcgDexCardInfo>(t => t.CardId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
modelBuilder.Entity<Set>()
|
||||||
|
.HasOne(s => s.SetInfo)
|
||||||
|
.WithOne(t => t.Set)
|
||||||
|
.HasForeignKey<TcgDexSetInfo>(t => t.SetIdRef)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
modelBuilder.Entity<Serie>()
|
||||||
|
.HasOne(s => s.TcgDexSeriesInfo)
|
||||||
|
.WithOne(t => t.Series)
|
||||||
|
.HasForeignKey<TcgDexSeriesInfo>(t => t.SeriesIdRef)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
modelBuilder.Entity<Set>()
|
||||||
|
.HasOne(s => s.CardCount)
|
||||||
|
.WithOne(c => c.Set)
|
||||||
|
.HasForeignKey<CardCount>(c => c.SetId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
modelBuilder.Entity<Set>()
|
||||||
|
.HasOne(s => s.CardMarketSetInfo)
|
||||||
|
.WithOne(cms => cms.Set)
|
||||||
|
.HasForeignKey<CardMarketSetInfo>(cms => cms.SetId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
// Auflösung der geteilten Legal-Tabelle
|
||||||
|
modelBuilder.Entity<Legal>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasOne(l => l.Set)
|
||||||
|
.WithOne(s => s.Legal)
|
||||||
|
.HasForeignKey<Legal>(l => l.SetId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
entity.HasOne(l => l.Card)
|
||||||
|
.WithOne(c => c.Legal)
|
||||||
|
.HasForeignKey<Legal>(l => l.CardId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Postgres JSONB Columns
|
||||||
|
|
||||||
|
modelBuilder.Entity<Card>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasOne(c => c.Set)
|
||||||
|
.WithMany(s => s.Cards)
|
||||||
|
.HasForeignKey(c => c.SetId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
// Native .NET Listen als JSONB in Postgres speichern
|
||||||
|
entity.Property(c => c.Variants).HasColumnType("jsonb");
|
||||||
|
entity.Property(c => c.Types).HasColumnType("jsonb");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Attack>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasOne(a => a.Card)
|
||||||
|
.WithMany(c => c.Attacks)
|
||||||
|
.HasForeignKey(a => a.CardId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
entity.Property(a => a.Cost).HasColumnType("jsonb");
|
||||||
|
});
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region CollectedCard & Grading
|
||||||
|
|
||||||
|
modelBuilder.Entity<CollectedCard>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasOne(cc => cc.Collection)
|
||||||
|
.WithMany(c => c.Cards)
|
||||||
|
.HasForeignKey(cc => cc.CollectionId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
entity.HasOne(cc => cc.Card)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(cc => cc.CardId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
entity.HasOne(cc => cc.ScannedBy)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(cc => cc.ScannedById)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Grading>()
|
||||||
|
.HasOne(g => g.CollectedCard)
|
||||||
|
.WithOne(cc => cc.Grading)
|
||||||
|
.HasForeignKey<Grading>(g => g.CollectedCardId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using OakArchive.Entities.Cards;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.CardMarket;
|
||||||
|
|
||||||
|
public class CardMarketSetInfo
|
||||||
|
{
|
||||||
|
[Key] public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public string ExpansionId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[ForeignKey(nameof(Set))] public Guid SetId { get; set; }
|
||||||
|
public virtual Set.Set Set { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.Cards;
|
||||||
|
|
||||||
|
public class Attack
|
||||||
|
{
|
||||||
|
[Key] public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public List<string> Cost { get; set; } = [];
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public int? Damage { get; set; }
|
||||||
|
|
||||||
|
public string Effect { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[ForeignKey(nameof(Card))] public Guid CardId { get; set; }
|
||||||
|
public virtual Card Card { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using OakArchive.Entities.CardMarket;
|
||||||
|
using OakArchive.Entities.Enums;
|
||||||
|
using OakArchive.Entities.Set;
|
||||||
|
using OakArchive.Entities.TCGdex;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.Cards;
|
||||||
|
|
||||||
|
public class Card
|
||||||
|
{
|
||||||
|
[Key] public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
[Required] public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Image { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public int CardNumber { get; set; } = 0;
|
||||||
|
|
||||||
|
public ulong PHash { get; set; } = 0;
|
||||||
|
|
||||||
|
public int? Hp { get; set; }
|
||||||
|
|
||||||
|
public int? Retreat { get; set; }
|
||||||
|
|
||||||
|
public string? TrainerType { get; set; }
|
||||||
|
|
||||||
|
public string? EnergyType { get; set; }
|
||||||
|
|
||||||
|
public string? Illustrator { get; set; }
|
||||||
|
|
||||||
|
public string? EvolveFrom { get; set; }
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
public string? RegulationMark { get; set; }
|
||||||
|
|
||||||
|
public Rarity? Rarity { get; set; }
|
||||||
|
|
||||||
|
public Category? Category { get; set; }
|
||||||
|
|
||||||
|
[Column(TypeName = "jsonb")]
|
||||||
|
public List<Variant>? Variants { get; set; }
|
||||||
|
|
||||||
|
[Column(TypeName = "jsonb")] public List<string>? Types { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey(nameof(Set))] public Guid SetId { get; set; }
|
||||||
|
public virtual Set.Set Set { get; set; } = null!;
|
||||||
|
|
||||||
|
public virtual TcgDexCardInfo TcgDexCardInfo { get; set; } = null!;
|
||||||
|
|
||||||
|
public virtual Legal? Legal { get; set; }
|
||||||
|
|
||||||
|
public virtual ICollection<Attack> Attacks { get; set; } = new List<Attack>();
|
||||||
|
|
||||||
|
public virtual ICollection<Weakness> Weaknesses { get; set; } = new List<Weakness>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using OakArchive.Entities.Enums;
|
||||||
|
using OakArchive.Entities.User;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.Cards;
|
||||||
|
|
||||||
|
public class CollectedCard
|
||||||
|
{
|
||||||
|
[Key] public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public Variant Variant { get; set; } = Variant.Normal;
|
||||||
|
|
||||||
|
public Condition Condition { get; set; } = Condition.Mint;
|
||||||
|
|
||||||
|
public int Count { get; set; } = 1;
|
||||||
|
|
||||||
|
public Language Language { get; set; } = Language.En;
|
||||||
|
|
||||||
|
public DateTime ScannedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
[ForeignKey(nameof(ScannedBy))] public Guid ScannedById { get; set; }
|
||||||
|
public ApplicationUser ScannedBy { get; set; } = null!;
|
||||||
|
|
||||||
|
[ForeignKey(nameof(Collection))] public Guid CollectionId { get; set; }
|
||||||
|
public virtual Collection.Collection Collection { get; set; } = null!;
|
||||||
|
|
||||||
|
[ForeignKey(nameof(Card))] public Guid CardId { get; set; }
|
||||||
|
public virtual Card Card { get; set; } = null!;
|
||||||
|
|
||||||
|
public virtual Grading.Grading? Grading { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.Cards;
|
||||||
|
|
||||||
|
public class Weakness
|
||||||
|
{
|
||||||
|
[Key] public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public string Type { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Value { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[ForeignKey(nameof(Card))] public Guid CardId { get; set; }
|
||||||
|
public virtual Card Card { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using OakArchive.Entities.Cards;
|
||||||
|
using OakArchive.Entities.User;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.Collection;
|
||||||
|
|
||||||
|
public class Collection
|
||||||
|
{
|
||||||
|
|
||||||
|
[Key]
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
public Guid CreatedBy { get; set; } = Guid.Empty;
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public string InviteCode { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public bool IsPublic { get; set; } = false;
|
||||||
|
|
||||||
|
public virtual ICollection<ApplicationUser> Members { get; set; } = [];
|
||||||
|
|
||||||
|
public virtual ICollection<CollectedCard> Cards { get; set; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.Enums;
|
||||||
|
|
||||||
|
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||||
|
public enum Category
|
||||||
|
{
|
||||||
|
[JsonPropertyName("Energy")]
|
||||||
|
Energy,
|
||||||
|
|
||||||
|
[JsonPropertyName("Pokemon")]
|
||||||
|
Pokemon,
|
||||||
|
|
||||||
|
[JsonPropertyName("Trainer")]
|
||||||
|
Trainer
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace OakArchive.Entities.Enums;
|
||||||
|
|
||||||
|
public enum Condition
|
||||||
|
{
|
||||||
|
Mint = 0,
|
||||||
|
NearMint = 1,
|
||||||
|
Excellent = 2,
|
||||||
|
Good = 3,
|
||||||
|
LightPlayed = 4,
|
||||||
|
Played = 5,
|
||||||
|
Poor = 6
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace OakArchive.Entities.Enums;
|
||||||
|
|
||||||
|
public enum GradingCompany
|
||||||
|
{
|
||||||
|
PSA,
|
||||||
|
BGS,
|
||||||
|
CGC,
|
||||||
|
APGrading,
|
||||||
|
EGS,
|
||||||
|
AOG,
|
||||||
|
PCA,
|
||||||
|
GSG,
|
||||||
|
Other
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.Enums;
|
||||||
|
|
||||||
|
public enum Language
|
||||||
|
{
|
||||||
|
En,
|
||||||
|
Fr,
|
||||||
|
Es,
|
||||||
|
Es_Mx,
|
||||||
|
It,
|
||||||
|
Pt,
|
||||||
|
Pt_Br,
|
||||||
|
Pt_Pt,
|
||||||
|
De,
|
||||||
|
Nl,
|
||||||
|
Pl,
|
||||||
|
Ru,
|
||||||
|
Ja,
|
||||||
|
Ko,
|
||||||
|
Zh_Tw,
|
||||||
|
Id,
|
||||||
|
Th,
|
||||||
|
Zh_Cn
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.Enums;
|
||||||
|
|
||||||
|
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||||
|
public enum Rarity
|
||||||
|
{
|
||||||
|
[JsonPropertyName("ACE SPEC Rare")]
|
||||||
|
AceSpecRare,
|
||||||
|
|
||||||
|
[JsonPropertyName("Amazing Rare")]
|
||||||
|
AmazingRare,
|
||||||
|
|
||||||
|
[JsonPropertyName("Black White Rare")]
|
||||||
|
BlackWhiteRare,
|
||||||
|
|
||||||
|
[JsonPropertyName("Classic Collection")]
|
||||||
|
ClassicCollection,
|
||||||
|
|
||||||
|
[JsonPropertyName("Common")]
|
||||||
|
Common,
|
||||||
|
|
||||||
|
[JsonPropertyName("Crown")]
|
||||||
|
Crown,
|
||||||
|
|
||||||
|
[JsonPropertyName("Double rare")]
|
||||||
|
DoubleRare,
|
||||||
|
|
||||||
|
[JsonPropertyName("Four Diamond")]
|
||||||
|
FourDiamond,
|
||||||
|
|
||||||
|
[JsonPropertyName("Full Art Trainer")]
|
||||||
|
FullArtTrainer,
|
||||||
|
|
||||||
|
[JsonPropertyName("Holo Rare")]
|
||||||
|
HoloRare,
|
||||||
|
|
||||||
|
[JsonPropertyName("Holo Rare V")]
|
||||||
|
HoloRareV,
|
||||||
|
|
||||||
|
[JsonPropertyName("Holo Rare VMAX")]
|
||||||
|
HoloRareVmax,
|
||||||
|
|
||||||
|
[JsonPropertyName("Holo Rare VSTAR")]
|
||||||
|
HoloRareVstar,
|
||||||
|
|
||||||
|
[JsonPropertyName("Hyper rare")]
|
||||||
|
HyperRare,
|
||||||
|
|
||||||
|
[JsonPropertyName("Illustration rare")]
|
||||||
|
IllustrationRare,
|
||||||
|
|
||||||
|
[JsonPropertyName("LEGEND")]
|
||||||
|
Legend,
|
||||||
|
|
||||||
|
[JsonPropertyName("Mega Hyper Rare")]
|
||||||
|
MegaHyperRare,
|
||||||
|
|
||||||
|
[JsonPropertyName("None")]
|
||||||
|
None,
|
||||||
|
|
||||||
|
[JsonPropertyName("One Diamond")]
|
||||||
|
OneDiamond,
|
||||||
|
|
||||||
|
[JsonPropertyName("One Shiny")]
|
||||||
|
OneShiny,
|
||||||
|
|
||||||
|
[JsonPropertyName("One Star")]
|
||||||
|
OneStar,
|
||||||
|
|
||||||
|
[JsonPropertyName("Radiant Rare")]
|
||||||
|
RadiantRare,
|
||||||
|
|
||||||
|
[JsonPropertyName("Rare")]
|
||||||
|
Rare,
|
||||||
|
|
||||||
|
[JsonPropertyName("Rare Holo")]
|
||||||
|
RareHolo,
|
||||||
|
|
||||||
|
[JsonPropertyName("Rare Holo LV.X")]
|
||||||
|
RareHoloLvX,
|
||||||
|
|
||||||
|
[JsonPropertyName("Rare PRIME")]
|
||||||
|
RarePrime,
|
||||||
|
|
||||||
|
[JsonPropertyName("Secret Rare")]
|
||||||
|
SecretRare,
|
||||||
|
|
||||||
|
[JsonPropertyName("Shiny Ultra Rare")]
|
||||||
|
ShinyUltraRare,
|
||||||
|
|
||||||
|
[JsonPropertyName("Shiny rare")]
|
||||||
|
ShinyRare,
|
||||||
|
|
||||||
|
[JsonPropertyName("Shiny rare V")]
|
||||||
|
ShinyRareV,
|
||||||
|
|
||||||
|
[JsonPropertyName("Shiny rare VMAX")]
|
||||||
|
ShinyRareVmax,
|
||||||
|
|
||||||
|
[JsonPropertyName("Special illustration rare")]
|
||||||
|
SpecialIllustrationRare,
|
||||||
|
|
||||||
|
[JsonPropertyName("Three Diamond")]
|
||||||
|
ThreeDiamond,
|
||||||
|
|
||||||
|
[JsonPropertyName("Three Star")]
|
||||||
|
ThreeStar,
|
||||||
|
|
||||||
|
[JsonPropertyName("Two Diamond")]
|
||||||
|
TwoDiamond,
|
||||||
|
|
||||||
|
[JsonPropertyName("Two Shiny")]
|
||||||
|
TwoShiny,
|
||||||
|
|
||||||
|
[JsonPropertyName("Two Star")]
|
||||||
|
TwoStar,
|
||||||
|
|
||||||
|
[JsonPropertyName("Ultra Rare")]
|
||||||
|
UltraRare,
|
||||||
|
|
||||||
|
[JsonPropertyName("Uncommon")]
|
||||||
|
Uncommon
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.Enums;
|
||||||
|
|
||||||
|
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||||
|
public enum Stage
|
||||||
|
{
|
||||||
|
[JsonPropertyName("BREAK")]
|
||||||
|
Break,
|
||||||
|
|
||||||
|
[JsonPropertyName("Basic")]
|
||||||
|
Basic,
|
||||||
|
|
||||||
|
[JsonPropertyName("LEVEL-UP")]
|
||||||
|
LevelUp,
|
||||||
|
|
||||||
|
[JsonPropertyName("MEGA")]
|
||||||
|
Mega,
|
||||||
|
|
||||||
|
[JsonPropertyName("RESTORED")]
|
||||||
|
Restored,
|
||||||
|
|
||||||
|
[JsonPropertyName("Stage1")]
|
||||||
|
Stage1,
|
||||||
|
|
||||||
|
[JsonPropertyName("Stage2")]
|
||||||
|
Stage2,
|
||||||
|
|
||||||
|
[JsonPropertyName("V-UNION")]
|
||||||
|
VUnion,
|
||||||
|
|
||||||
|
[JsonPropertyName("VMAX")]
|
||||||
|
VMax,
|
||||||
|
|
||||||
|
[JsonPropertyName("VSTAR")]
|
||||||
|
VStar
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.Enums;
|
||||||
|
|
||||||
|
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||||
|
public enum Variant
|
||||||
|
{
|
||||||
|
[JsonPropertyName("firstEdition")]
|
||||||
|
FirstEdition,
|
||||||
|
|
||||||
|
[JsonPropertyName("holo")]
|
||||||
|
Holo,
|
||||||
|
|
||||||
|
[JsonPropertyName("normal")]
|
||||||
|
Normal,
|
||||||
|
|
||||||
|
[JsonPropertyName("reverse")]
|
||||||
|
Reverse,
|
||||||
|
|
||||||
|
[JsonPropertyName("wPromo")]
|
||||||
|
WPromo
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using OakArchive.Entities.Cards;
|
||||||
|
using OakArchive.Entities.Enums;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.Grading;
|
||||||
|
|
||||||
|
public class Grading
|
||||||
|
{
|
||||||
|
[Key] public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
[Required] public GradingCompany Company { get; set; } = GradingCompany.PSA;
|
||||||
|
[Required] public string CertificateNumber { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public double Grade { get; set; }
|
||||||
|
|
||||||
|
public double? Centering { get; set; }
|
||||||
|
public double? Corners { get; set; }
|
||||||
|
public double? Edges { get; set; }
|
||||||
|
public double? Surface { get; set; }
|
||||||
|
|
||||||
|
public string? GraderNotes { get; set; }
|
||||||
|
|
||||||
|
public DateTime GradedAt { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey(nameof(CollectedCard))] public Guid CollectedCardId { get; set; }
|
||||||
|
public virtual CollectedCard CollectedCard { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using OakArchive.Entities.Enums;
|
||||||
|
using OakArchive.Entities.TCGdex;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.Serie;
|
||||||
|
|
||||||
|
public class Serie
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? Logo { get; set; }
|
||||||
|
|
||||||
|
public Language? Language { get; set; }
|
||||||
|
|
||||||
|
//public DateTime? ReleaseDate { get; set; }
|
||||||
|
|
||||||
|
public virtual ICollection<Set.Set> Sets { get; set; } = new List<Set.Set>();
|
||||||
|
|
||||||
|
public virtual TcgDexSeriesInfo TcgDexSeriesInfo { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.Set;
|
||||||
|
|
||||||
|
public class CardCount
|
||||||
|
{
|
||||||
|
[Key] public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public int Total { get; set; } = 0;
|
||||||
|
|
||||||
|
public int Official { get; set; } = 0;
|
||||||
|
|
||||||
|
public int Normal { get; set; } = 0;
|
||||||
|
|
||||||
|
public int Reverse { get; set; } = 0;
|
||||||
|
|
||||||
|
public int Holo { get; set; } = 0;
|
||||||
|
|
||||||
|
public int FirstEd { get; set; } = 0;
|
||||||
|
|
||||||
|
[ForeignKey(nameof(Set))]
|
||||||
|
public Guid SetId { get; set; }
|
||||||
|
|
||||||
|
public virtual Set Set { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using OakArchive.Entities.Cards;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.Set;
|
||||||
|
|
||||||
|
public class Legal
|
||||||
|
{
|
||||||
|
[Key] public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public bool Expanded { get; set; } = false;
|
||||||
|
|
||||||
|
public bool Standard { get; set; } = false;
|
||||||
|
|
||||||
|
[ForeignKey(nameof(Set))]
|
||||||
|
public Guid? SetId { get; set; }
|
||||||
|
public virtual Set? Set { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey(nameof(Card))]
|
||||||
|
public Guid? CardId { get; set; }
|
||||||
|
public virtual Card? Card { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using OakArchive.Entities.CardMarket;
|
||||||
|
using OakArchive.Entities.Cards;
|
||||||
|
using OakArchive.Entities.Enums;
|
||||||
|
using OakArchive.Entities.TCGdex;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.Set;
|
||||||
|
|
||||||
|
public class Set
|
||||||
|
{
|
||||||
|
[Key] public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
[Required] public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? Symbol { get; set; }
|
||||||
|
|
||||||
|
public string? Logo { get; set; }
|
||||||
|
|
||||||
|
public Language Language { get; set; } = Language.En;
|
||||||
|
|
||||||
|
public DateTime? ReleaseDate { get; set; }
|
||||||
|
|
||||||
|
public ICollection<Card> Cards { get; set; } = new List<Card>();
|
||||||
|
|
||||||
|
public virtual TcgDexSetInfo SetInfo { get; set; } = null!;
|
||||||
|
|
||||||
|
public virtual CardMarketSetInfo CardMarketSetInfo { get; set; } = null!;
|
||||||
|
|
||||||
|
[ForeignKey(nameof(Serie))] public Guid SerieId { get; set; }
|
||||||
|
public virtual Serie.Serie Serie { get; set; } = null!;
|
||||||
|
|
||||||
|
public virtual CardCount CardCount { get; set; } = null!;
|
||||||
|
|
||||||
|
public virtual Legal? Legal { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using OakArchive.Entities.Cards;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.TCGdex;
|
||||||
|
|
||||||
|
public class TcgDexCardInfo
|
||||||
|
{
|
||||||
|
[Key] public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
[Required] public string FullId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Required] public int LocalId { get; set; } = 0;
|
||||||
|
|
||||||
|
[ForeignKey(nameof(Card))] public Guid CardId { get; set; }
|
||||||
|
public virtual Card Card { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.TCGdex;
|
||||||
|
|
||||||
|
public class TcgDexSeriesInfo
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string SeriesId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[ForeignKey(nameof(Set))]
|
||||||
|
public Guid SeriesIdRef { get; set; }
|
||||||
|
public virtual Serie.Serie Series { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.TCGdex;
|
||||||
|
|
||||||
|
public class TcgDexSetInfo
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string SetId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[ForeignKey(nameof(Set))]
|
||||||
|
public Guid SetIdRef { get; set; }
|
||||||
|
public virtual Set.Set Set { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.User;
|
||||||
|
|
||||||
|
public class ApplicationUser : IdentityUser<Guid>
|
||||||
|
{
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public virtual Settings Settings { get; set; } = new();
|
||||||
|
|
||||||
|
private Collection.Collection DefaultCollection { get; set; } = new();
|
||||||
|
|
||||||
|
public virtual ICollection<Collection.Collection> Collections { get; set; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace OakArchive.Entities.User;
|
||||||
|
|
||||||
|
public class Settings
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public bool AddCardsToDefaultList { get; set; } = true;
|
||||||
|
|
||||||
|
[ForeignKey(nameof(User))]
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
public virtual ApplicationUser User { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace OakArchive.Models.TcgDex;
|
||||||
|
|
||||||
|
public class Attack
|
||||||
|
{
|
||||||
|
[JsonPropertyName("cost")]
|
||||||
|
public List<string>? Cost { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("effect")]
|
||||||
|
public string? Effect { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("damage")]
|
||||||
|
public int? Damage { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using OakArchive.Entities.Set;
|
||||||
|
|
||||||
|
namespace OakArchive.Models.TcgDex;
|
||||||
|
|
||||||
|
public class Card
|
||||||
|
{
|
||||||
|
[JsonPropertyName("category")]
|
||||||
|
public string? Category { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public string? Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("illustrator")]
|
||||||
|
public string? Illustrator { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("image")]
|
||||||
|
public string? Image { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("localId")]
|
||||||
|
public int? LocalId { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("rarity")]
|
||||||
|
public string? Rarity { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("set")]
|
||||||
|
public Entities.Set.Set? Set { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("variants")]
|
||||||
|
public Variants? Variants { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("hp")]
|
||||||
|
public int? Hp { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("types")]
|
||||||
|
public List<string>? Types { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("evolveFrom")]
|
||||||
|
public string? EvolveFrom { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("description")]
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("stage")]
|
||||||
|
public string? Stage { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("attacks")]
|
||||||
|
public List<Attack>? Attacks { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("weaknesses")]
|
||||||
|
public List<Weakness>? Weaknesses { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("retreat")]
|
||||||
|
public int? Retreat { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("regulationMark")]
|
||||||
|
public string? RegulationMark { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("legal")]
|
||||||
|
public Legal? Legal { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace OakArchive.Models.TcgDex;
|
||||||
|
|
||||||
|
public class CardCount
|
||||||
|
{
|
||||||
|
[JsonPropertyName("firstEd")]
|
||||||
|
public int? FirstEd { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("holo")]
|
||||||
|
public int? Holo { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("normal")]
|
||||||
|
public int? Normal { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("official")]
|
||||||
|
public int? Official { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("reverse")]
|
||||||
|
public int? Reverse { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("total")]
|
||||||
|
public int? Total { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace OakArchive.Models.TcgDex;
|
||||||
|
|
||||||
|
public class Legal
|
||||||
|
{
|
||||||
|
[JsonPropertyName("standard")]
|
||||||
|
public bool? Standard { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("expanded")]
|
||||||
|
public bool? Expanded { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace OakArchive.Models.TcgDex;
|
||||||
|
|
||||||
|
public class Serie
|
||||||
|
{
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public string? Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("logo")]
|
||||||
|
public string? Logo { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("sets")]
|
||||||
|
public List<SetBrief>? Sets { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace OakArchive.Models.TcgDex;
|
||||||
|
|
||||||
|
public class SerieBrief
|
||||||
|
{
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public string? Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("logo")]
|
||||||
|
public string? Logo { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace OakArchive.Models.TcgDex;
|
||||||
|
|
||||||
|
public class Set
|
||||||
|
{
|
||||||
|
[JsonPropertyName("cardCount")]
|
||||||
|
public CardCount CardCount { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("cards")]
|
||||||
|
public List<Card> Cards { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("legal")]
|
||||||
|
public Legal Legal { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("logo")]
|
||||||
|
public string Logo { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("releaseDate")]
|
||||||
|
public DateTime ReleaseDate { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("serie")]
|
||||||
|
public Serie Serie { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("symbol")]
|
||||||
|
public string Symbol { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace OakArchive.Models.TcgDex;
|
||||||
|
|
||||||
|
public class SetBrief
|
||||||
|
{
|
||||||
|
[JsonPropertyName("cardCount")]
|
||||||
|
public CardCount? CardCount { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public string? Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("logo")]
|
||||||
|
public string? Logo { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("symbol")]
|
||||||
|
public string? Symbol { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace OakArchive.Models.TcgDex;
|
||||||
|
|
||||||
|
public class Variants
|
||||||
|
{
|
||||||
|
[JsonPropertyName("firstEdition")]
|
||||||
|
public bool? FirstEdition { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("holo")]
|
||||||
|
public bool? Holo { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("normal")]
|
||||||
|
public bool? Normal { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("reverse")]
|
||||||
|
public bool? Reverse { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("wPromo")]
|
||||||
|
public bool? WPromo { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace OakArchive.Models.TcgDex;
|
||||||
|
|
||||||
|
public class Weakness
|
||||||
|
{
|
||||||
|
[JsonPropertyName("type")]
|
||||||
|
public string? Type { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("value")]
|
||||||
|
public string? Value { get; set; }
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CoenM.ImageSharp.ImageHash" Version="1.3.6" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.7" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.7" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.7" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.7" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7">
|
||||||
@@ -28,13 +29,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Controllers\" />
|
<Folder Include="Controllers\V1\" />
|
||||||
<Folder Include="Entities\CardMarket\" />
|
|
||||||
<Folder Include="Entities\Cards\" />
|
|
||||||
<Folder Include="Entities\Collection\" />
|
|
||||||
<Folder Include="Entities\TCGdex\" />
|
|
||||||
<Folder Include="Entities\User\" />
|
|
||||||
<Folder Include="Models\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
+49
-3
@@ -1,21 +1,67 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using OakArchive.Database;
|
||||||
|
using OakArchive.Services;
|
||||||
|
|
||||||
namespace OakArchive;
|
namespace OakArchive;
|
||||||
|
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Add services to the container.
|
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
|
|
||||||
|
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
||||||
|
options.UseNpgsql(builder.Configuration["DefaultConnection"]));
|
||||||
|
|
||||||
|
|
||||||
|
builder.Services.AddMemoryCache(); // Registriert das native IMemoryCache von Microsoft
|
||||||
|
builder.Services.AddSingleton<IMemoryCacheService, MemoryCacheService>();
|
||||||
|
|
||||||
|
builder.Services.AddHttpClient<ITcgDexService, TcgDexService>(client =>
|
||||||
|
{
|
||||||
|
client.BaseAddress = new Uri("https://api.tcgdex.net/v2/");
|
||||||
|
client.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddScoped<ITcgDexDataUpdater, TcgDexDataUpdater>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
app.Lifetime.ApplicationStarted.Register(async () =>
|
||||||
|
{
|
||||||
|
// Da ApplicationStarted synchron registriert, machen wir hier einen neuen asynchronen Scope auf
|
||||||
|
using var scope = app.Services.CreateScope();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
|
||||||
|
Console.WriteLine("[INFO] Prüfe auf ausstehende Datenbank-Migrationen...");
|
||||||
|
|
||||||
|
// Führt alle ausstehenden Migrationen auf der PostgreSQL-Datenbank aus.
|
||||||
|
// Falls die Datenbank noch gar nicht existiert, wird sie hier automatisch erstellt!
|
||||||
|
await context.Database.MigrateAsync();
|
||||||
|
|
||||||
|
var updater = scope.ServiceProvider.GetRequiredService<ITcgDexDataUpdater>();
|
||||||
|
|
||||||
|
Console.WriteLine("[INFO] === Starte TCGdex Test-Sync im Hintergrund ===");
|
||||||
|
|
||||||
|
var result = await updater.SyncBaseDataFromTcgDex();
|
||||||
|
|
||||||
|
Console.WriteLine($"[INFO] === Test-Sync beendet! Neue Daten geladen: {result} ===");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[FEHLER] !!! Fehler beim Test-Sync: {ex.Message} !!!");
|
||||||
|
Console.WriteLine(ex.StackTrace);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.UseStaticFiles();
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
|
||||||
|
namespace OakArchive.Services;
|
||||||
|
|
||||||
|
public interface IMemoryCacheService
|
||||||
|
{
|
||||||
|
Task<T?> GetOrCreateAsync<T>(string cacheKey, Func<Task<T?>> createItem, TimeSpan? expiration = null)
|
||||||
|
where T : class;
|
||||||
|
|
||||||
|
void Remove(string cacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MemoryCacheService : IMemoryCacheService
|
||||||
|
{
|
||||||
|
private readonly IMemoryCache _memoryCache;
|
||||||
|
private readonly TimeSpan _defaultExpiration = TimeSpan.FromHours(1);
|
||||||
|
|
||||||
|
public MemoryCacheService(IMemoryCache memoryCache)
|
||||||
|
{
|
||||||
|
_memoryCache = memoryCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<T?> GetOrCreateAsync<T>(string cacheKey, Func<Task<T?>> createItem, TimeSpan? expiration = null)
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
if (_memoryCache.TryGetValue(cacheKey, out T? cachedItem))
|
||||||
|
{
|
||||||
|
return cachedItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = await createItem();
|
||||||
|
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cacheOptions = new MemoryCacheEntryOptions()
|
||||||
|
.SetAbsoluteExpiration(expiration ?? _defaultExpiration);
|
||||||
|
|
||||||
|
_memoryCache.Set(cacheKey, item, cacheOptions);
|
||||||
|
|
||||||
|
return item!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(string cacheKey)
|
||||||
|
{
|
||||||
|
_memoryCache.Remove(cacheKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using OakArchive.Database;
|
||||||
|
using OakArchive.Entities.CardMarket;
|
||||||
|
using OakArchive.Entities.Cards;
|
||||||
|
using OakArchive.Entities.Enums;
|
||||||
|
using OakArchive.Entities.TCGdex;
|
||||||
|
using OakArchive.Util;
|
||||||
|
using OakArchive.Util.Extensions;
|
||||||
|
using Card = OakArchive.Entities.Cards.Card;
|
||||||
|
using CardCount = OakArchive.Entities.Set.CardCount;
|
||||||
|
using Legal = OakArchive.Entities.Set.Legal;
|
||||||
|
using Set = OakArchive.Entities.Set.Set;
|
||||||
|
|
||||||
|
namespace OakArchive.Services;
|
||||||
|
|
||||||
|
public interface ITcgDexDataUpdater
|
||||||
|
{
|
||||||
|
public Task<bool> SyncBaseDataFromTcgDex();
|
||||||
|
|
||||||
|
public Task<Card>
|
||||||
|
UpdateCard(string set, int id, string language);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TcgDexDataUpdater : ITcgDexDataUpdater
|
||||||
|
{
|
||||||
|
private readonly ITcgDexService _tcgDexService;
|
||||||
|
private readonly ApplicationDbContext _dbContext;
|
||||||
|
|
||||||
|
private readonly List<Language> _languages = Enum.GetValues<Language>().ToList();
|
||||||
|
|
||||||
|
public TcgDexDataUpdater(ITcgDexService tcgDexService, ApplicationDbContext dbContext)
|
||||||
|
{
|
||||||
|
_tcgDexService = tcgDexService;
|
||||||
|
_dbContext = dbContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SyncBaseDataFromTcgDex()
|
||||||
|
{
|
||||||
|
var hasChanges = false;
|
||||||
|
int i = 0;
|
||||||
|
foreach (var language in _languages)
|
||||||
|
{
|
||||||
|
var apiSeriesBriefs = await _tcgDexService.GetSeries(language.ToApiCode());
|
||||||
|
|
||||||
|
await Task.Delay(500);
|
||||||
|
|
||||||
|
foreach (var serieBrief in apiSeriesBriefs)
|
||||||
|
{
|
||||||
|
if (i == 1) return true;
|
||||||
|
i += 1;
|
||||||
|
if (string.IsNullOrEmpty(serieBrief.Id)) continue;
|
||||||
|
|
||||||
|
var dbSerie = await EnsureSerieExistsAsync(serieBrief, language);
|
||||||
|
|
||||||
|
var fullApiSerie = await _tcgDexService.GetSetsOfSeries(serieBrief.Id, language.ToApiCode());
|
||||||
|
if (fullApiSerie?.Sets == null) continue;
|
||||||
|
|
||||||
|
await Task.Delay(300);
|
||||||
|
|
||||||
|
foreach (var setBrief in fullApiSerie.Sets)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(setBrief.Id)) continue;
|
||||||
|
|
||||||
|
var isSetInDb = dbSerie.Sets.Any(s => s.SetInfo?.SetId == setBrief.Id);
|
||||||
|
if (isSetInDb) continue;
|
||||||
|
|
||||||
|
var fullApiSet = await _tcgDexService.GetSet(setBrief.Id, language.ToApiCode());
|
||||||
|
if (fullApiSet == null) continue;
|
||||||
|
|
||||||
|
var dbSet = await BuildSetEntityWithCardsAsync(setBrief, fullApiSet, language);
|
||||||
|
|
||||||
|
dbSerie.Sets.Add(dbSet);
|
||||||
|
|
||||||
|
await Task.Delay(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await _dbContext.SaveChangesAsync() > 0)
|
||||||
|
{
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Card> UpdateCard(string set, int id, string language)
|
||||||
|
{
|
||||||
|
var tcgDexCardId = $"{set.ToLower()}-{id}";
|
||||||
|
if (!Enum.TryParse<Language>(language, true, out var langEnum))
|
||||||
|
{
|
||||||
|
langEnum = Language.En;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dbCard = await _dbContext.Cards
|
||||||
|
.Include(c => c.TcgDexCardInfo)
|
||||||
|
.Include(c => c.Attacks)
|
||||||
|
.Include(c => c.Weaknesses)
|
||||||
|
.FirstOrDefaultAsync(c => c.TcgDexCardInfo.FullId == tcgDexCardId && c.Set.Language == langEnum);
|
||||||
|
|
||||||
|
if (dbCard == null)
|
||||||
|
{
|
||||||
|
throw new KeyNotFoundException(
|
||||||
|
$"Die Basis-Karte mit der TCGdex-ID '{tcgDexCardId}' wurde für die Sprache '{language}' nicht in der Datenbank gefunden. Führe zuerst den Basis-Sync aus.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiCard = await _tcgDexService.GetCard(set, id, language);
|
||||||
|
if (apiCard == null)
|
||||||
|
{
|
||||||
|
throw new HttpRequestException(
|
||||||
|
$"Die Karte '{tcgDexCardId}' konnte von der TCGdex-API nicht geladen werden.");
|
||||||
|
}
|
||||||
|
|
||||||
|
dbCard.Name = apiCard.Name ?? dbCard.Name;
|
||||||
|
dbCard.Hp = apiCard.Hp;
|
||||||
|
dbCard.Retreat = apiCard.Retreat;
|
||||||
|
dbCard.Illustrator = apiCard.Illustrator;
|
||||||
|
dbCard.EvolveFrom = apiCard.EvolveFrom;
|
||||||
|
dbCard.Description = apiCard.Description;
|
||||||
|
dbCard.RegulationMark = apiCard.RegulationMark;
|
||||||
|
dbCard.Types = apiCard.Types;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(apiCard.Rarity) &&
|
||||||
|
Enum.TryParse<Rarity>(apiCard.Rarity.Replace(" ", ""), true, out var parsedRarity))
|
||||||
|
{
|
||||||
|
dbCard.Rarity = parsedRarity;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(apiCard.Category) &&
|
||||||
|
Enum.TryParse<Category>(apiCard.Category, true, out var parsedCategory))
|
||||||
|
{
|
||||||
|
dbCard.Category = parsedCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
dbCard.Variants = MapApiVariantsToEnumList(apiCard.Variants);
|
||||||
|
|
||||||
|
dbCard.Attacks.Clear();
|
||||||
|
if (apiCard.Attacks != null)
|
||||||
|
{
|
||||||
|
foreach (var apiAttack in apiCard.Attacks)
|
||||||
|
{
|
||||||
|
dbCard.Attacks.Add(new Attack
|
||||||
|
{
|
||||||
|
Name = apiAttack.Name ?? "Unknown",
|
||||||
|
Damage = apiAttack.Damage,
|
||||||
|
Effect = apiAttack.Effect ?? string.Empty,
|
||||||
|
Cost = apiAttack.Cost ?? []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbCard.Weaknesses.Clear();
|
||||||
|
if (apiCard.Weaknesses != null)
|
||||||
|
{
|
||||||
|
foreach (var apiWeakness in apiCard.Weaknesses)
|
||||||
|
{
|
||||||
|
dbCard.Weaknesses.Add(new Weakness
|
||||||
|
{
|
||||||
|
Type = apiWeakness.Type ?? "Unknown",
|
||||||
|
Value = apiWeakness.Value ?? string.Empty
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
return dbCard;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Variant> MapApiVariantsToEnumList(Models.TcgDex.Variants? apiVariants)
|
||||||
|
{
|
||||||
|
var list = new List<Variant>();
|
||||||
|
if (apiVariants == null) return list;
|
||||||
|
|
||||||
|
if (apiVariants.FirstEdition == true) list.Add(Variant.FirstEdition);
|
||||||
|
if (apiVariants.Holo == true) list.Add(Variant.Holo);
|
||||||
|
if (apiVariants.Normal == true) list.Add(Variant.Normal);
|
||||||
|
if (apiVariants.Reverse == true) list.Add(Variant.Reverse);
|
||||||
|
if (apiVariants.WPromo == true) list.Add(Variant.WPromo);
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Entities.Serie.Serie> EnsureSerieExistsAsync(Models.TcgDex.SerieBrief brief, Language language)
|
||||||
|
{
|
||||||
|
var dbSerie = await _dbContext.Series
|
||||||
|
.Include(s => s.Sets)
|
||||||
|
.ThenInclude(s => s.SetInfo)
|
||||||
|
.FirstOrDefaultAsync(s => s.TcgDexSeriesInfo.SeriesId == brief.Id && s.Language == language);
|
||||||
|
|
||||||
|
if (dbSerie != null) return dbSerie;
|
||||||
|
|
||||||
|
dbSerie = new Entities.Serie.Serie
|
||||||
|
{
|
||||||
|
Language = language,
|
||||||
|
Name = brief.Name ?? "Unknown",
|
||||||
|
Logo = brief.Logo,
|
||||||
|
TcgDexSeriesInfo = new TcgDexSeriesInfo { SeriesId = brief.Id! }
|
||||||
|
};
|
||||||
|
_dbContext.Series.Add(dbSerie);
|
||||||
|
|
||||||
|
return dbSerie;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Set> BuildSetEntityWithCardsAsync(Models.TcgDex.SetBrief setBrief, Models.TcgDex.Set fullApiSet,
|
||||||
|
Language language)
|
||||||
|
{
|
||||||
|
DateTime? utcReleaseDate = fullApiSet.ReleaseDate != null
|
||||||
|
? DateTime.SpecifyKind(fullApiSet.ReleaseDate, DateTimeKind.Utc)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
var dbSet = new Set
|
||||||
|
{
|
||||||
|
Language = language,
|
||||||
|
Name = setBrief.Name ?? "Unknown",
|
||||||
|
Logo = setBrief.Logo,
|
||||||
|
ReleaseDate = utcReleaseDate,
|
||||||
|
Symbol = setBrief.Symbol,
|
||||||
|
SetInfo = new TcgDexSetInfo { SetId = fullApiSet.Id },
|
||||||
|
CardMarketSetInfo = new CardMarketSetInfo { ExpansionId = "0" }, // TODO: Späterer Price-Scraper
|
||||||
|
Legal = new Legal
|
||||||
|
{
|
||||||
|
Expanded = fullApiSet.Legal?.Expanded ?? false,
|
||||||
|
Standard = fullApiSet.Legal?.Standard ?? false
|
||||||
|
},
|
||||||
|
CardCount = new CardCount
|
||||||
|
{
|
||||||
|
FirstEd = fullApiSet.CardCount?.FirstEd ?? 0,
|
||||||
|
Holo = fullApiSet.CardCount?.Holo ?? 0,
|
||||||
|
Normal = fullApiSet.CardCount?.Normal ?? 0,
|
||||||
|
Official = fullApiSet.CardCount?.Official ?? 0,
|
||||||
|
Reverse = fullApiSet.CardCount?.Reverse ?? 0,
|
||||||
|
Total = fullApiSet.CardCount?.Total ?? 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fullApiSet.Cards is not { Count: > 0 }) return dbSet;
|
||||||
|
|
||||||
|
var cardTasks = fullApiSet.Cards.Select(async c =>
|
||||||
|
{
|
||||||
|
var localPath = await _tcgDexService.DownloadCardImage(c, language.ToApiCode());
|
||||||
|
|
||||||
|
return new Card
|
||||||
|
{
|
||||||
|
Name = c.Name ?? "Unknown",
|
||||||
|
Image = localPath == null ? c.Image : localPath.Item1,
|
||||||
|
CardNumber = c.LocalId ?? 0,
|
||||||
|
PHash = localPath == null ? 0 : PerceptualHashHelper.GenerateHash(localPath.Item2),
|
||||||
|
TcgDexCardInfo = new TcgDexCardInfo
|
||||||
|
{
|
||||||
|
FullId = c.Id ?? string.Empty,
|
||||||
|
LocalId = c.LocalId ?? 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
dbSet.Cards = (await Task.WhenAll(cardTasks)).ToList();
|
||||||
|
|
||||||
|
return dbSet;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
using OakArchive.Models.TcgDex;
|
||||||
|
|
||||||
|
namespace OakArchive.Services;
|
||||||
|
|
||||||
|
public interface ITcgDexService
|
||||||
|
{
|
||||||
|
public Task<List<SerieBrief>> GetSeries(string language);
|
||||||
|
|
||||||
|
public Task<Serie?> GetSetsOfSeries(string series, string language);
|
||||||
|
|
||||||
|
public Task<Set?> GetSet(string set, string language);
|
||||||
|
|
||||||
|
public Task<Card?> GetCard(string set, int id, string language);
|
||||||
|
|
||||||
|
public Task<Tuple<string, byte[]>?> DownloadCardImage(Card card, string language);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TcgDexService : ITcgDexService
|
||||||
|
{
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly IMemoryCacheService _cacheService;
|
||||||
|
|
||||||
|
private readonly string _baseFolder = Path.Combine(Directory.GetCurrentDirectory(), "Cards");
|
||||||
|
private readonly SemaphoreSlim _downloadSemaphore = new SemaphoreSlim(3, 3);
|
||||||
|
|
||||||
|
public TcgDexService(HttpClient httpClient, IMemoryCacheService cacheService)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_cacheService = cacheService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<SerieBrief>> GetSeries(string language)
|
||||||
|
{
|
||||||
|
var cacheKey = $"tcgdex_series_{language.ToLower()}";
|
||||||
|
|
||||||
|
async Task<List<SerieBrief>> FetchFromApi()
|
||||||
|
{
|
||||||
|
var res = await _httpClient.GetFromJsonAsync<List<SerieBrief>>($"{language}/series");
|
||||||
|
return res ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _cacheService.GetOrCreateAsync(cacheKey, FetchFromApi, TimeSpan.FromDays(7));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Serie?> GetSetsOfSeries(string series, string language)
|
||||||
|
{
|
||||||
|
var cacheKey = $"tcgdex_serie_{series.ToLower()}_{language.ToLower()}";
|
||||||
|
|
||||||
|
async Task<Serie?> FetchFromApi()
|
||||||
|
{
|
||||||
|
var res = await _httpClient.GetFromJsonAsync<Serie>($"{language}/series/{series}");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _cacheService.GetOrCreateAsync(cacheKey, FetchFromApi, TimeSpan.FromDays(7));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Set?> GetSet(string set, string language)
|
||||||
|
{
|
||||||
|
var cacheKey = $"tcgdex_set_{set.ToLower()}_{language.ToLower()}";
|
||||||
|
|
||||||
|
async Task<Set?> FetchFromApi()
|
||||||
|
{
|
||||||
|
var res = await _httpClient.GetFromJsonAsync<Set>($"{language}/sets/{set}");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _cacheService.GetOrCreateAsync(cacheKey, FetchFromApi, TimeSpan.FromDays(7));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Card?> GetCard(string set, int id, string language)
|
||||||
|
{
|
||||||
|
var cacheKey = $"tcgdex_card_{set.ToLower()}_{language.ToLower()}";
|
||||||
|
|
||||||
|
async Task<Card?> FetchFromApi()
|
||||||
|
{
|
||||||
|
var res = await _httpClient.GetFromJsonAsync<Card?>($"{language}/cards/{set}-{id}");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _cacheService.GetOrCreateAsync(cacheKey, FetchFromApi, TimeSpan.FromDays(7));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Tuple<string, byte[]>?> DownloadCardImage(Card card, string language)
|
||||||
|
{
|
||||||
|
var split = card.Id!.Split("-");
|
||||||
|
var filePath = Path.Combine(_baseFolder, split[0], language,$"{split[1]}.webp");
|
||||||
|
|
||||||
|
if (File.Exists(filePath))
|
||||||
|
{
|
||||||
|
var res = await File.ReadAllBytesAsync(filePath);
|
||||||
|
return Tuple.Create(filePath, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _downloadSemaphore.WaitAsync();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var res = await _httpClient.GetByteArrayAsync(card.Image + "/high.webp");
|
||||||
|
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(filePath)!);
|
||||||
|
await File.WriteAllBytesAsync(filePath, res);
|
||||||
|
|
||||||
|
await Task.Delay(200);
|
||||||
|
|
||||||
|
return Tuple.Create(filePath, res);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_downloadSemaphore.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using OakArchive.Entities.Enums;
|
||||||
|
|
||||||
|
namespace OakArchive.Util.Extensions;
|
||||||
|
|
||||||
|
public static class LanguageExtensions
|
||||||
|
{
|
||||||
|
public static string ToApiCode(this Language lang)
|
||||||
|
{
|
||||||
|
return lang switch
|
||||||
|
{
|
||||||
|
Language.Es_Mx => "es-mx",
|
||||||
|
Language.Pt_Br => "pt-br",
|
||||||
|
Language.Pt_Pt => "pt-pt",
|
||||||
|
Language.Zh_Tw => "zh-tw",
|
||||||
|
Language.Zh_Cn => "zh-cn",
|
||||||
|
_ => lang.ToString().ToLower()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ein zusätzliches Feld für deine Avalonia-UI (Anzeigename)
|
||||||
|
public static string GetDisplayName(this Language lang)
|
||||||
|
{
|
||||||
|
return lang switch
|
||||||
|
{
|
||||||
|
Language.En => "English",
|
||||||
|
Language.Fr => "Français",
|
||||||
|
Language.Es => "Español",
|
||||||
|
Language.Es_Mx => "Español (México)",
|
||||||
|
Language.It => "Italiano",
|
||||||
|
Language.Pt => "Português",
|
||||||
|
Language.Pt_Br => "Português (Brasil)",
|
||||||
|
Language.Pt_Pt => "Português (Portugal)",
|
||||||
|
Language.De => "Deutsch",
|
||||||
|
Language.Nl => "Nederlands",
|
||||||
|
Language.Pl => "Polski",
|
||||||
|
Language.Ru => "Русский",
|
||||||
|
Language.Ja => "日本語",
|
||||||
|
Language.Ko => "한국어",
|
||||||
|
Language.Zh_Tw => "繁體中文 (Taiwan)",
|
||||||
|
Language.Id => "Bahasa Indonesia",
|
||||||
|
Language.Th => "ไทย",
|
||||||
|
Language.Zh_Cn => "简体中文 (China)",
|
||||||
|
_ => lang.ToString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using CoenM.ImageHash.HashAlgorithms;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
|
||||||
|
namespace OakArchive.Util;
|
||||||
|
|
||||||
|
public class PerceptualHashHelper
|
||||||
|
{
|
||||||
|
private static readonly PerceptualHash _perceptualHash = new();
|
||||||
|
|
||||||
|
private const int TargetWidth = 600;
|
||||||
|
private const int TargetHeight = 840;
|
||||||
|
private const int Margin = 15;
|
||||||
|
|
||||||
|
public static ulong GenerateHash(byte[] rawImage)
|
||||||
|
{
|
||||||
|
using var image = Image.Load<Rgba32>(rawImage);
|
||||||
|
|
||||||
|
image.Mutate(x => x.Resize(TargetWidth, TargetHeight));
|
||||||
|
|
||||||
|
return _perceptualHash.Hash(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ulong GenerateHashWithMargin(byte[] rawImage)
|
||||||
|
{
|
||||||
|
using var image = Image.Load<Rgba32>(rawImage);
|
||||||
|
|
||||||
|
const int maxContentWidth = TargetWidth - (2 * Margin);
|
||||||
|
const int maxContentHeight = TargetHeight - (2 * Margin);
|
||||||
|
|
||||||
|
image.Mutate(x => x.Resize(new ResizeOptions
|
||||||
|
{
|
||||||
|
Size = new Size(maxContentWidth, maxContentHeight),
|
||||||
|
Mode = ResizeMode.Max
|
||||||
|
}));
|
||||||
|
|
||||||
|
using var canvas = new Image<Rgba32>(TargetWidth, TargetHeight, Color.Black);
|
||||||
|
|
||||||
|
var posX = (TargetWidth - image.Width) / 2;
|
||||||
|
var posY = (TargetHeight - image.Height) / 2;
|
||||||
|
|
||||||
|
canvas.Mutate(x => x.DrawImage(image, new Point(posX, posY), 1f));
|
||||||
|
|
||||||
|
return _perceptualHash.Hash(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-4
@@ -4,13 +4,12 @@
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: OakArchive/Dockerfile
|
dockerfile: OakArchive/Dockerfile
|
||||||
# Falls die DB in einer anderen Datei ist, nimm depends_on raus,
|
|
||||||
# da Compose nur Services innerhalb derselben Datei überwachen kann.
|
|
||||||
networks:
|
networks:
|
||||||
- postgres-network
|
- postgres-network
|
||||||
environment:
|
environment:
|
||||||
# WICHTIG: Host muss der 'container_name' oder 'service_name' der DB sein
|
- DefaultConnection=Host=OmniDB;Database=oak_db;Username=admin;Password=${DB_PASSWORD}
|
||||||
- CONNECTION_STRING=Host=OmniDB;Database=oak_db;Username=admin;Password=${DB_PASSWORD}
|
volumes:
|
||||||
|
- C:\Users\larsh\Documents\docker\oakarchive\Cards:/app/Cards
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
postgres-network:
|
postgres-network:
|
||||||
|
|||||||
Reference in New Issue
Block a user