Migrate to Blazor WASM, MSSQL platform DB, and durable audio PVC.
build-and-deploy / deploy (push) Failing after 15m11s
build-and-deploy / deploy (push) Failing after 15m11s
Remove in-cluster Postgres; serve WASM via nginx; add audio import job for Gooya archives.
This commit is contained in:
@@ -56,23 +56,33 @@ public static class TafsirEndpoints
|
||||
return Results.Ok(detail);
|
||||
});
|
||||
|
||||
group.MapGet("/audio/{episodeId:guid}", async (Guid episodeId, AppDbContext db, IWebHostEnvironment env, CancellationToken ct) =>
|
||||
group.MapGet("/audio/{episodeId:guid}", async (Guid episodeId, AppDbContext db, IConfiguration config, IWebHostEnvironment env, CancellationToken ct) =>
|
||||
{
|
||||
var episode = await db.Episodes.AsNoTracking().FirstOrDefaultAsync(e => e.Id == episodeId, ct);
|
||||
if (episode is null) return Results.NotFound();
|
||||
|
||||
var relative = episode.AudioUrl.TrimStart('/');
|
||||
var path = Path.Combine(env.ContentRootPath, "wwwroot", relative);
|
||||
if (!File.Exists(path))
|
||||
var audioRoot = config["AudioRoot"] ?? "/data/audio";
|
||||
var relative = episode.AudioUrl.TrimStart('/').Replace('\\', '/');
|
||||
|
||||
// Prefer PVC mount; fallback to wwwroot for local sample files
|
||||
var candidates = new[]
|
||||
{
|
||||
// Fallback placeholder: return empty audio-friendly response path for demo
|
||||
var placeholder = Path.Combine(env.ContentRootPath, "wwwroot", "audio", "silence.mp3");
|
||||
if (File.Exists(placeholder))
|
||||
return Results.File(placeholder, "audio/mpeg", enableRangeProcessing: true);
|
||||
return Results.NotFound();
|
||||
Path.Combine(audioRoot, relative),
|
||||
Path.Combine(env.ContentRootPath, "wwwroot", relative),
|
||||
Path.Combine(env.ContentRootPath, "wwwroot", "audio", Path.GetFileName(relative))
|
||||
};
|
||||
|
||||
foreach (var path in candidates)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
return Results.File(path, "audio/mpeg", enableRangeProcessing: true);
|
||||
}
|
||||
|
||||
return Results.File(path, "audio/mpeg", enableRangeProcessing: true);
|
||||
var silence = Path.Combine(env.ContentRootPath, "wwwroot", "audio", "silence.mp3");
|
||||
if (File.Exists(silence))
|
||||
return Results.File(silence, "audio/mpeg", enableRangeProcessing: true);
|
||||
|
||||
return Results.NotFound();
|
||||
});
|
||||
|
||||
return group;
|
||||
|
||||
@@ -47,7 +47,8 @@ using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
try
|
||||
{
|
||||
await DbSeeder.SeedAsync(db);
|
||||
var audioRoot = app.Configuration["AudioRoot"] ?? "/data/audio";
|
||||
await DbSeeder.SeedAsync(db, audioRoot);
|
||||
break;
|
||||
}
|
||||
catch (Exception ex) when (attempt < 30)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"Default": "Host=localhost;Port=5434;Database=tafsirquran;Username=tafsir;Password=tafsir_dev"
|
||||
"Default": "Server=localhost,31433;Database=TafsirQuran;User Id=sa;Password=87zH26nbqT;TrustServerCertificate=True"
|
||||
},
|
||||
"AudioRoot": "/data/audio",
|
||||
"Jwt": {
|
||||
"Key": "TafsirQuran-Dev-Secret-Key-Change-In-Production-32+",
|
||||
"Issuer": "TafsirQuran",
|
||||
|
||||
@@ -10,10 +10,10 @@ public static class DependencyInjection
|
||||
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var connectionString = configuration.GetConnectionString("Default")
|
||||
?? "Host=localhost;Port=5434;Database=tafsirquran;Username=tafsir;Password=tafsir_dev";
|
||||
?? "Server=localhost,31433;Database=TafsirQuran;User Id=sa;Password=87zH26nbqT;TrustServerCertificate=True";
|
||||
|
||||
services.AddDbContext<AppDbContext>(options =>
|
||||
options.UseNpgsql(connectionString));
|
||||
options.UseSqlServer(connectionString));
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -7,114 +7,154 @@ public static class DbSeeder
|
||||
{
|
||||
public static readonly Guid GhatrehChekaniId = Guid.Parse("11111111-1111-1111-1111-111111111111");
|
||||
|
||||
public static async Task SeedAsync(AppDbContext db, CancellationToken ct = default)
|
||||
private static readonly (string Ar, string Fa)[] SurahNames =
|
||||
[
|
||||
("الفاتحة", "فاتحه"), ("البقرة", "بقره"), ("آل عمران", "آل عمران"), ("النساء", "نساء"),
|
||||
("المائدة", "مائده"), ("الأنعام", "انعام"), ("الأعراف", "اعراف"), ("الأنفال", "انفال"),
|
||||
("التوبة", "توبه"), ("يونس", "یونس"), ("هود", "هود"), ("يوسف", "یوسف"),
|
||||
("الرعد", "رعد"), ("إبراهيم", "ابراهیم"), ("الحجر", "حجر"), ("النحل", "نحل"),
|
||||
("الإسراء", "اسراء"), ("الكهف", "کهف"), ("مريم", "مریم"), ("طه", "طه"),
|
||||
("الأنبياء", "انبیاء"), ("الحج", "حج"), ("المؤمنون", "مؤمنون"), ("النور", "نور"),
|
||||
("الفرقان", "فرقان"), ("الشعراء", "شعراء"), ("النمل", "نمل"), ("القصص", "قصص"),
|
||||
("العنكبوت", "عنکبوت"), ("الروم", "روم"), ("لقمان", "لقمان"), ("السجدة", "سجده"),
|
||||
("الأحزاب", "احزاب"), ("سبأ", "سبأ"), ("فاطر", "فاطر"), ("يس", "یس"),
|
||||
("الصافات", "صافات"), ("ص", "ص"), ("الزمر", "زمر"), ("غافر", "غافر"),
|
||||
("فصلت", "فصلت"), ("الشورى", "شوری"), ("الزخرف", "زخرف"), ("الدخان", "دخان"),
|
||||
("الجاثية", "جاثیه"), ("الأحقاف", "احقاف"), ("محمد", "محمد"), ("الفتح", "فتح"),
|
||||
("الحجرات", "حجرات"), ("ق", "ق"), ("الذاريات", "ذاریات"), ("الطور", "طور"),
|
||||
("النجم", "نجم"), ("القمر", "قمر"), ("الرحمن", "رحمن"), ("الواقعة", "واقعه"),
|
||||
("الحديد", "حدید"), ("المجادلة", "مجادله"), ("الحشر", "حشر"), ("الممتحنة", "ممتحنه"),
|
||||
("الصف", "صف"), ("الجمعة", "جمعه"), ("المنافقون", "منافقون"), ("التغابن", "تغابن"),
|
||||
("الطلاق", "طلاق"), ("التحريم", "تحریم"), ("الملك", "ملک"), ("القلم", "قلم"),
|
||||
("الحاقة", "حاقه"), ("المعارج", "معارج"), ("نوح", "نوح"), ("الجن", "جن"),
|
||||
("المزمل", "مزمل"), ("المدثر", "مدثر"), ("القيامة", "قیامه"), ("الإنسان", "انسان"),
|
||||
("المرسلات", "مرسلات"), ("النبأ", "نبأ"), ("النازعات", "نازعات"), ("عبس", "عبس"),
|
||||
("التكوير", "تکویر"), ("الانفطار", "انفطار"), ("المطففين", "مطففین"), ("الانشقاق", "انشقاق"),
|
||||
("البروج", "بروج"), ("الطارق", "طارق"), ("الأعلى", "اعلی"), ("الغاشية", "غاشیه"),
|
||||
("الفجر", "فجر"), ("البلد", "بلد"), ("الشمس", "شمس"), ("الليل", "لیل"),
|
||||
("الضحى", "ضحی"), ("الشرح", "شرح"), ("التين", "تین"), ("العلق", "علق"),
|
||||
("القدر", "قدر"), ("البينة", "بینه"), ("الزلزلة", "زلزله"), ("العاديات", "عادیات"),
|
||||
("القارعة", "قارعه"), ("التكاثر", "تکاثر"), ("العصر", "عصر"), ("الهمزة", "همزه"),
|
||||
("الفيل", "فیل"), ("قريش", "قریش"), ("الماعون", "ماعون"), ("الكوثر", "کوثر"),
|
||||
("الكافرون", "کافرون"), ("النصر", "نصر"), ("المسد", "مسد"), ("الإخلاص", "اخلاص"),
|
||||
("الفلق", "فلق"), ("الناس", "ناس")
|
||||
];
|
||||
|
||||
public static async Task SeedAsync(AppDbContext db, string? audioRoot = null, CancellationToken ct = default)
|
||||
{
|
||||
await db.Database.MigrateAsync(ct);
|
||||
|
||||
if (!await db.Surahs.AnyAsync(ct))
|
||||
{
|
||||
db.Surahs.AddRange(
|
||||
new Surah { Id = 1, Number = 1, NameAr = "الفاتحة", NameFa = "فاتحه" },
|
||||
new Surah { Id = 2, Number = 2, NameAr = "البقرة", NameFa = "بقره" },
|
||||
new Surah { Id = 112, Number = 112, NameAr = "الإخلاص", NameFa = "اخلاص" }
|
||||
);
|
||||
|
||||
db.Ayahs.AddRange(
|
||||
new Ayah
|
||||
for (var i = 0; i < SurahNames.Length; i++)
|
||||
{
|
||||
var n = i + 1;
|
||||
db.Surahs.Add(new Surah
|
||||
{
|
||||
SurahId = 1,
|
||||
Number = 1,
|
||||
TextAr = "بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ",
|
||||
TextFa = "به نام خداوند بخشنده مهربان"
|
||||
},
|
||||
new Ayah
|
||||
{
|
||||
SurahId = 1,
|
||||
Number = 2,
|
||||
TextAr = "الْحَمْدُ لِلَّهِ رَبِّ الْعَالَمِينَ",
|
||||
TextFa = "ستایش مخصوص خداوندی است که پروردگار جهانیان است"
|
||||
},
|
||||
new Ayah
|
||||
{
|
||||
SurahId = 1,
|
||||
Number = 3,
|
||||
TextAr = "الرَّحْمَٰنِ الرَّحِيمِ",
|
||||
TextFa = "بخشنده مهربان"
|
||||
},
|
||||
new Ayah
|
||||
{
|
||||
SurahId = 112,
|
||||
Number = 1,
|
||||
TextAr = "قُلْ هُوَ اللَّهُ أَحَدٌ",
|
||||
TextFa = "بگو اوست خدای یگانه"
|
||||
}
|
||||
);
|
||||
Id = n,
|
||||
Number = n,
|
||||
NameAr = SurahNames[i].Ar,
|
||||
NameFa = SurahNames[i].Fa
|
||||
});
|
||||
}
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
if (!await db.Tafsirs.AnyAsync(ct))
|
||||
{
|
||||
var tafsir = new Tafsir
|
||||
db.Tafsirs.Add(new Tafsir
|
||||
{
|
||||
Id = GhatrehChekaniId,
|
||||
Title = "تفسیر قطره چکانی",
|
||||
Author = "قرائتی",
|
||||
Slug = "ghatreh-chekani",
|
||||
IsPublished = true
|
||||
};
|
||||
db.Tafsirs.Add(tafsir);
|
||||
|
||||
db.Episodes.AddRange(
|
||||
new TafsirEpisode
|
||||
{
|
||||
Id = Guid.Parse("22222222-2222-2222-2222-222222222201"),
|
||||
TafsirId = GhatrehChekaniId,
|
||||
SurahId = 1,
|
||||
AyahNumber = 1,
|
||||
Title = "فاتحه آیه ۱",
|
||||
AudioUrl = "/audio/sample-1.mp3",
|
||||
DurationSeconds = 180,
|
||||
SortOrder = 1,
|
||||
Summary = "تفسیر قطرهچکانی سوره فاتحه آیه اول"
|
||||
},
|
||||
new TafsirEpisode
|
||||
{
|
||||
Id = Guid.Parse("22222222-2222-2222-2222-222222222202"),
|
||||
TafsirId = GhatrehChekaniId,
|
||||
SurahId = 1,
|
||||
AyahNumber = 2,
|
||||
Title = "فاتحه آیه ۲",
|
||||
AudioUrl = "/audio/sample-2.mp3",
|
||||
DurationSeconds = 210,
|
||||
SortOrder = 2,
|
||||
Summary = "تفسیر قطرهچکانی سوره فاتحه آیه دوم"
|
||||
},
|
||||
new TafsirEpisode
|
||||
{
|
||||
Id = Guid.Parse("22222222-2222-2222-2222-222222222203"),
|
||||
TafsirId = GhatrehChekaniId,
|
||||
SurahId = 1,
|
||||
AyahNumber = 3,
|
||||
Title = "فاتحه آیه ۳",
|
||||
AudioUrl = "/audio/sample-3.mp3",
|
||||
DurationSeconds = 195,
|
||||
SortOrder = 3,
|
||||
Summary = "تفسیر قطرهچکانی سوره فاتحه آیه سوم"
|
||||
},
|
||||
new TafsirEpisode
|
||||
{
|
||||
Id = Guid.Parse("22222222-2222-2222-2222-222222222204"),
|
||||
TafsirId = GhatrehChekaniId,
|
||||
SurahId = 112,
|
||||
AyahNumber = 1,
|
||||
Title = "اخلاص آیه ۱",
|
||||
AudioUrl = "/audio/sample-4.mp3",
|
||||
DurationSeconds = 160,
|
||||
SortOrder = 4,
|
||||
Summary = "تفسیر قطرهچکانی سوره اخلاص آیه اول"
|
||||
}
|
||||
);
|
||||
});
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync(ct);
|
||||
await SyncEpisodesFromAudioAsync(db, audioRoot, ct);
|
||||
}
|
||||
|
||||
public static async Task SyncEpisodesFromAudioAsync(AppDbContext db, string? audioRoot, CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(audioRoot) || !Directory.Exists(audioRoot))
|
||||
return;
|
||||
|
||||
var tafsir = await db.Tafsirs.FirstOrDefaultAsync(t => t.Id == GhatrehChekaniId, ct);
|
||||
if (tafsir is null) return;
|
||||
|
||||
var existing = await db.Episodes
|
||||
.Where(e => e.TafsirId == GhatrehChekaniId)
|
||||
.Select(e => e.AudioUrl)
|
||||
.ToListAsync(ct);
|
||||
var existingSet = existing.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var audioFiles = Directory.EnumerateFiles(audioRoot, "*.*", SearchOption.AllDirectories)
|
||||
.Where(f => f.EndsWith(".mp3", StringComparison.OrdinalIgnoreCase)
|
||||
|| f.EndsWith(".m4a", StringComparison.OrdinalIgnoreCase)
|
||||
|| f.EndsWith(".ogg", StringComparison.OrdinalIgnoreCase)
|
||||
|| f.EndsWith(".wav", StringComparison.OrdinalIgnoreCase))
|
||||
.OrderBy(f => f, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
if (audioFiles.Count == 0) return;
|
||||
|
||||
var sort = existing.Count;
|
||||
var added = 0;
|
||||
foreach (var file in audioFiles)
|
||||
{
|
||||
var relative = Path.GetRelativePath(audioRoot, file).Replace('\\', '/');
|
||||
if (existingSet.Contains(relative)) continue;
|
||||
|
||||
var surahNumber = GuessSurahNumber(relative, file);
|
||||
var ayahNumber = GuessAyahNumber(Path.GetFileNameWithoutExtension(file));
|
||||
sort++;
|
||||
db.Episodes.Add(new TafsirEpisode
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TafsirId = GhatrehChekaniId,
|
||||
SurahId = surahNumber,
|
||||
AyahNumber = ayahNumber,
|
||||
Title = $"{SurahNames[surahNumber - 1].Fa} — {Path.GetFileNameWithoutExtension(file)}",
|
||||
AudioUrl = relative,
|
||||
DurationSeconds = 0,
|
||||
SortOrder = sort,
|
||||
Summary = null
|
||||
});
|
||||
added++;
|
||||
}
|
||||
|
||||
if (added > 0)
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
private static int GuessSurahNumber(string relative, string fullPath)
|
||||
{
|
||||
var parts = relative.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var digits = new string(part.TakeWhile(char.IsDigit).ToArray());
|
||||
if (digits.Length >= 1 && int.TryParse(digits, out var n) && n is >= 1 and <= 114)
|
||||
return n;
|
||||
}
|
||||
|
||||
var name = Path.GetFileName(fullPath);
|
||||
var nameDigits = new string(name.TakeWhile(char.IsDigit).ToArray());
|
||||
if (nameDigits.Length >= 1 && int.TryParse(nameDigits, out var fromName) && fromName is >= 1 and <= 114)
|
||||
return fromName;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int GuessAyahNumber(string fileName)
|
||||
{
|
||||
// patterns: ..._001, -1, .1, ayah01
|
||||
var matches = System.Text.RegularExpressions.Regex.Matches(fileName, @"(\d+)");
|
||||
if (matches.Count == 0) return 1;
|
||||
// prefer last number group as ayah when multiple
|
||||
if (int.TryParse(matches[^1].Value, out var n) && n > 0)
|
||||
return n;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
+56
-54
@@ -2,9 +2,9 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using TafsirQuran.Infrastructure.Persistence;
|
||||
|
||||
#nullable disable
|
||||
@@ -12,8 +12,8 @@ using TafsirQuran.Infrastructure.Persistence;
|
||||
namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20261009174748_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
[Migration("20261009190049_InitialSqlServer")]
|
||||
partial class InitialSqlServer
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@@ -21,32 +21,32 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("TafsirQuran.Domain.Entities.AppUser", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -60,20 +60,20 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("Number")
|
||||
.HasColumnType("integer");
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SurahId")
|
||||
.HasColumnType("integer");
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("TextAr")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("TextFa")
|
||||
.HasColumnType("text");
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -87,25 +87,25 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<Guid>("EpisodeId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Note")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("character varying(500)");
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -114,7 +114,8 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("DeviceId", "UserId", "EpisodeId")
|
||||
.IsUnique();
|
||||
.IsUnique()
|
||||
.HasFilter("[UserId] IS NOT NULL");
|
||||
|
||||
b.ToTable("Bookmarks");
|
||||
});
|
||||
@@ -123,21 +124,21 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<Guid>("EpisodeId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -146,7 +147,8 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("DeviceId", "UserId", "EpisodeId")
|
||||
.IsUnique();
|
||||
.IsUnique()
|
||||
.HasFilter("[UserId] IS NOT NULL");
|
||||
|
||||
b.ToTable("Favorites");
|
||||
});
|
||||
@@ -155,24 +157,24 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<Guid>("EpisodeId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<double>("PositionSeconds")
|
||||
.HasColumnType("double precision");
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -189,22 +191,22 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
.HasColumnType("int");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("NameAr")
|
||||
.IsRequired()
|
||||
.HasMaxLength(80)
|
||||
.HasColumnType("character varying(80)");
|
||||
.HasColumnType("nvarchar(80)");
|
||||
|
||||
b.Property<string>("NameFa")
|
||||
.IsRequired()
|
||||
.HasMaxLength(80)
|
||||
.HasColumnType("character varying(80)");
|
||||
.HasColumnType("nvarchar(80)");
|
||||
|
||||
b.Property<int>("Number")
|
||||
.HasColumnType("integer");
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -218,28 +220,28 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Author")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("character varying(120)");
|
||||
.HasColumnType("nvarchar(120)");
|
||||
|
||||
b.Property<string>("CoverUrl")
|
||||
.HasColumnType("text");
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsPublished")
|
||||
.HasColumnType("boolean");
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("character varying(120)");
|
||||
.HasColumnType("nvarchar(120)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -253,34 +255,34 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("AudioUrl")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("AyahNumber")
|
||||
.HasColumnType("integer");
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("DurationSeconds")
|
||||
.HasColumnType("integer");
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("integer");
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Summary")
|
||||
.HasColumnType("text");
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("SurahId")
|
||||
.HasColumnType("integer");
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("TafsirId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("character varying(300)");
|
||||
.HasColumnType("nvarchar(300)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
+52
-51
@@ -1,13 +1,12 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCreate : Migration
|
||||
public partial class InitialSqlServer : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
@@ -16,11 +15,11 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
name: "Surahs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Number = table.Column<int>(type: "integer", nullable: false),
|
||||
NameAr = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: false),
|
||||
NameFa = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: false)
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
Number = table.Column<int>(type: "int", nullable: false),
|
||||
NameAr = table.Column<string>(type: "nvarchar(80)", maxLength: 80, nullable: false),
|
||||
NameFa = table.Column<string>(type: "nvarchar(80)", maxLength: 80, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@@ -31,12 +30,12 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
name: "Tafsirs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Title = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
Author = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: false),
|
||||
Slug = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: false),
|
||||
CoverUrl = table.Column<string>(type: "text", nullable: true),
|
||||
IsPublished = table.Column<bool>(type: "boolean", nullable: false)
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Title = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
||||
Author = table.Column<string>(type: "nvarchar(120)", maxLength: 120, nullable: false),
|
||||
Slug = table.Column<string>(type: "nvarchar(120)", maxLength: 120, nullable: false),
|
||||
CoverUrl = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
IsPublished = table.Column<bool>(type: "bit", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@@ -47,11 +46,11 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
name: "Users",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Email = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
PasswordHash = table.Column<string>(type: "text", nullable: false),
|
||||
DisplayName = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false)
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Email = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||
PasswordHash = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
DisplayName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@@ -62,11 +61,11 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
name: "Ayahs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
SurahId = table.Column<int>(type: "integer", nullable: false),
|
||||
Number = table.Column<int>(type: "integer", nullable: false),
|
||||
TextAr = table.Column<string>(type: "text", nullable: false),
|
||||
TextFa = table.Column<string>(type: "text", nullable: true)
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
SurahId = table.Column<int>(type: "int", nullable: false),
|
||||
Number = table.Column<int>(type: "int", nullable: false),
|
||||
TextAr = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
TextFa = table.Column<string>(type: "nvarchar(max)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@@ -83,15 +82,15 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
name: "Episodes",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
TafsirId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
SurahId = table.Column<int>(type: "integer", nullable: false),
|
||||
AyahNumber = table.Column<int>(type: "integer", nullable: false),
|
||||
Title = table.Column<string>(type: "character varying(300)", maxLength: 300, nullable: false),
|
||||
AudioUrl = table.Column<string>(type: "text", nullable: false),
|
||||
DurationSeconds = table.Column<int>(type: "integer", nullable: false),
|
||||
SortOrder = table.Column<int>(type: "integer", nullable: false),
|
||||
Summary = table.Column<string>(type: "text", nullable: true)
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
TafsirId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
SurahId = table.Column<int>(type: "int", nullable: false),
|
||||
AyahNumber = table.Column<int>(type: "int", nullable: false),
|
||||
Title = table.Column<string>(type: "nvarchar(300)", maxLength: 300, nullable: false),
|
||||
AudioUrl = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
DurationSeconds = table.Column<int>(type: "int", nullable: false),
|
||||
SortOrder = table.Column<int>(type: "int", nullable: false),
|
||||
Summary = table.Column<string>(type: "nvarchar(max)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@@ -114,12 +113,12 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
name: "Bookmarks",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
UserId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
DeviceId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
EpisodeId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Note = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: true),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false)
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DeviceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
EpisodeId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Note = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@@ -141,11 +140,11 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
name: "Favorites",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
UserId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
DeviceId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
EpisodeId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false)
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DeviceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
EpisodeId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@@ -167,12 +166,12 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
name: "ListeningProgresses",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
UserId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
DeviceId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
EpisodeId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
PositionSeconds = table.Column<double>(type: "double precision", nullable: false),
|
||||
UpdatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false)
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
DeviceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
EpisodeId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
PositionSeconds = table.Column<double>(type: "float", nullable: false),
|
||||
UpdatedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@@ -200,7 +199,8 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
name: "IX_Bookmarks_DeviceId_UserId_EpisodeId",
|
||||
table: "Bookmarks",
|
||||
columns: new[] { "DeviceId", "UserId", "EpisodeId" },
|
||||
unique: true);
|
||||
unique: true,
|
||||
filter: "[UserId] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Bookmarks_EpisodeId",
|
||||
@@ -226,7 +226,8 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
name: "IX_Favorites_DeviceId_UserId_EpisodeId",
|
||||
table: "Favorites",
|
||||
columns: new[] { "DeviceId", "UserId", "EpisodeId" },
|
||||
unique: true);
|
||||
unique: true,
|
||||
filter: "[UserId] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Favorites_EpisodeId",
|
||||
+54
-52
@@ -2,8 +2,8 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using TafsirQuran.Infrastructure.Persistence;
|
||||
|
||||
#nullable disable
|
||||
@@ -18,32 +18,32 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("TafsirQuran.Domain.Entities.AppUser", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -57,20 +57,20 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("Number")
|
||||
.HasColumnType("integer");
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SurahId")
|
||||
.HasColumnType("integer");
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("TextAr")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("TextFa")
|
||||
.HasColumnType("text");
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -84,25 +84,25 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<Guid>("EpisodeId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Note")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("character varying(500)");
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -111,7 +111,8 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("DeviceId", "UserId", "EpisodeId")
|
||||
.IsUnique();
|
||||
.IsUnique()
|
||||
.HasFilter("[UserId] IS NOT NULL");
|
||||
|
||||
b.ToTable("Bookmarks");
|
||||
});
|
||||
@@ -120,21 +121,21 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<Guid>("EpisodeId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -143,7 +144,8 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("DeviceId", "UserId", "EpisodeId")
|
||||
.IsUnique();
|
||||
.IsUnique()
|
||||
.HasFilter("[UserId] IS NOT NULL");
|
||||
|
||||
b.ToTable("Favorites");
|
||||
});
|
||||
@@ -152,24 +154,24 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<Guid>("EpisodeId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<double>("PositionSeconds")
|
||||
.HasColumnType("double precision");
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<Guid?>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -186,22 +188,22 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
.HasColumnType("int");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("NameAr")
|
||||
.IsRequired()
|
||||
.HasMaxLength(80)
|
||||
.HasColumnType("character varying(80)");
|
||||
.HasColumnType("nvarchar(80)");
|
||||
|
||||
b.Property<string>("NameFa")
|
||||
.IsRequired()
|
||||
.HasMaxLength(80)
|
||||
.HasColumnType("character varying(80)");
|
||||
.HasColumnType("nvarchar(80)");
|
||||
|
||||
b.Property<int>("Number")
|
||||
.HasColumnType("integer");
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -215,28 +217,28 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Author")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("character varying(120)");
|
||||
.HasColumnType("nvarchar(120)");
|
||||
|
||||
b.Property<string>("CoverUrl")
|
||||
.HasColumnType("text");
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("IsPublished")
|
||||
.HasColumnType("boolean");
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("character varying(120)");
|
||||
.HasColumnType("nvarchar(120)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -250,34 +252,34 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("AudioUrl")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("AyahNumber")
|
||||
.HasColumnType("integer");
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("DurationSeconds")
|
||||
.HasColumnType("integer");
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("integer");
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Summary")
|
||||
.HasColumnType("text");
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("SurahId")
|
||||
.HasColumnType("integer");
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("TafsirId")
|
||||
.HasColumnType("uuid");
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("character varying(300)");
|
||||
.HasColumnType("nvarchar(300)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.10">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.10" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
@using TafsirQuran.UI
|
||||
@using TafsirQuran.UI.Layout
|
||||
|
||||
<Router AppAssembly="typeof(Routes).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="routeData" DefaultLayout="typeof(AppLayout)" />
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="typeof(AppLayout)">
|
||||
<p class="muted">صفحه پیدا نشد.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
@@ -1,9 +0,0 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
@Body
|
||||
|
||||
<div id="blazor-error-ui" data-nosnippet>
|
||||
An unhandled error has occurred.
|
||||
<a href="." class="reload">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
@@ -1,20 +0,0 @@
|
||||
#blazor-error-ui {
|
||||
color-scheme: light only;
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
@page "/Error"
|
||||
@using System.Diagnostics
|
||||
|
||||
<PageTitle>Error</PageTitle>
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
||||
|
||||
@code{
|
||||
[CascadingParameter]
|
||||
private HttpContext? HttpContext { get; set; }
|
||||
|
||||
private string? RequestId { get; set; }
|
||||
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
protected override void OnInitialized() =>
|
||||
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
|
||||
}
|
||||
@@ -1,30 +1,13 @@
|
||||
using TafsirQuran.UI;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using TafsirQuran.UI.Services;
|
||||
using TafsirQuran.Web.Components;
|
||||
using TafsirQuran.Web;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
builder.RootComponents.Add<App>("#app");
|
||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||
|
||||
var apiBase = builder.Configuration["ApiBaseAddress"] ?? "http://localhost:5080/";
|
||||
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
|
||||
builder.Services.AddTafsirUi(apiBase);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
||||
}
|
||||
|
||||
// HTTP NodePort / reverse-proxy; do not force HTTPS redirect in cluster.
|
||||
app.UseAntiforgery();
|
||||
app.UseStaticFiles();
|
||||
app.MapStaticAssets();
|
||||
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode()
|
||||
.AddAdditionalAssemblies(typeof(TafsirQuran.UI.Routes).Assembly);
|
||||
|
||||
app.Run();
|
||||
await builder.Build().RunAsync();
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5299",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7169;http://localhost:5299",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "http://localhost:5299",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.10" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TafsirQuran.UI\TafsirQuran.UI.csproj" />
|
||||
<ProjectReference Include="..\TafsirQuran.Shared\TafsirQuran.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.AspNetCore.Components.WebAssembly.Http
|
||||
@using Microsoft.JSInterop
|
||||
@using TafsirQuran.UI
|
||||
@using TafsirQuran.UI.Layout
|
||||
@using TafsirQuran.UI.Pages
|
||||
@using TafsirQuran.UI.Components
|
||||
@using TafsirQuran.Web
|
||||
@using TafsirQuran.Web.Components
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"ApiBaseAddress": "http://localhost:5080/",
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"ApiBaseAddress": "http://localhost:5080/"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"ApiBaseAddress": "http://185.252.31.44:30821/"
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fa" dir="rtl">
|
||||
|
||||
<head>
|
||||
@@ -6,19 +6,22 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#1f6b4a" />
|
||||
<meta name="description" content="تفسیر قرآن کریم — صوت و متن" />
|
||||
<title>تفسیر قرآن</title>
|
||||
<base href="/" />
|
||||
<link rel="manifest" href="manifest.webmanifest" />
|
||||
<link rel="apple-touch-icon" href="icons/icon-192.png" />
|
||||
<link rel="stylesheet" href="_content/TafsirQuran.UI/css/app.css" />
|
||||
<link rel="stylesheet" href="@Assets["app.css"]" />
|
||||
<HeadOutlet @rendermode="InteractiveServer" />
|
||||
<link rel="stylesheet" href="app.css" />
|
||||
<link href="TafsirQuran.Web.styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<TafsirQuran.UI.Routes @rendermode="InteractiveServer" />
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
<div id="app">
|
||||
<div class="muted" style="padding:2rem;text-align:center">در حال بارگذاری...</div>
|
||||
</div>
|
||||
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
<script src="_content/TafsirQuran.UI/js/app.js"></script>
|
||||
<script src="service-worker-register.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,5 +0,0 @@
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('/service-worker.js').catch(() => {});
|
||||
});
|
||||
}
|
||||
@@ -1,25 +1,2 @@
|
||||
const CACHE = 'tafsirquran-static-v1';
|
||||
const ASSETS = [
|
||||
'/',
|
||||
'/manifest.webmanifest',
|
||||
'/_content/TafsirQuran.UI/css/app.css',
|
||||
'/_content/TafsirQuran.UI/js/app.js'
|
||||
];
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(caches.open(CACHE).then((cache) => cache.addAll(ASSETS)).then(() => self.skipWaiting()));
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(
|
||||
caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))).then(() => self.clients.claim())
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', (event) => {
|
||||
const req = event.request;
|
||||
if (req.method !== 'GET') return;
|
||||
event.respondWith(
|
||||
caches.match(req).then((cached) => cached || fetch(req).catch(() => caches.match('/')))
|
||||
);
|
||||
});
|
||||
// Development placeholder — published SW is service-worker.published.js
|
||||
self.addEventListener('fetch', () => { });
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Cache static WASM assets only — never HTML navigations.
|
||||
const CACHE = 'tafsirquran-wasm-v1';
|
||||
|
||||
self.addEventListener('install', event => event.waitUntil(self.skipWaiting()));
|
||||
self.addEventListener('activate', event => event.waitUntil(
|
||||
caches.keys().then(keys => Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k))))
|
||||
.then(() => self.clients.claim())
|
||||
));
|
||||
|
||||
self.addEventListener('fetch', event => {
|
||||
const req = event.request;
|
||||
if (req.method !== 'GET') return;
|
||||
const url = new URL(req.url);
|
||||
if (req.mode === 'navigate' || (req.headers.get('accept') || '').includes('text/html')) {
|
||||
event.respondWith(fetch(req));
|
||||
return;
|
||||
}
|
||||
if (url.pathname.startsWith('/api/') || url.origin.includes(':30821')) {
|
||||
event.respondWith(fetch(req));
|
||||
return;
|
||||
}
|
||||
event.respondWith(
|
||||
caches.open(CACHE).then(async cache => {
|
||||
const cached = await cache.match(req);
|
||||
if (cached) return cached;
|
||||
const res = await fetch(req);
|
||||
if (res.ok) cache.put(req, res.clone());
|
||||
return res;
|
||||
})
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user