enable users to suggest geo location tags #421

This commit is contained in:
Hamid Reza Mohammadi 2026-09-18 17:34:30 +03:30
parent 47011429f5
commit f6bd5a756c
8 changed files with 6670 additions and 1 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,79 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace RMuseum.Migrations
{
/// <inheritdoc />
public partial class AddPoemGeoSuggestion : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "GanjoorPoemGeoDateTagCorrection",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
CoupletIndex = table.Column<int>(type: "int", nullable: true),
LocationId = table.Column<int>(type: "int", nullable: true),
SuggestedLocationName = table.Column<string>(type: "nvarchar(max)", nullable: true),
SuggestedLatitude = table.Column<double>(type: "float", nullable: true),
SuggestedLongitude = table.Column<double>(type: "float", nullable: true),
LunarYear = table.Column<int>(type: "int", nullable: true),
LunarMonth = table.Column<int>(type: "int", nullable: true),
LunarDay = table.Column<int>(type: "int", nullable: true),
PersonId = table.Column<int>(type: "int", nullable: true),
IgnoreInCategory = table.Column<bool>(type: "bit", nullable: false),
SuggestionNote = table.Column<string>(type: "nvarchar(max)", nullable: true),
MarkForDelete = table.Column<bool>(type: "bit", nullable: false),
ExistingTagId = table.Column<int>(type: "int", nullable: true),
Result = table.Column<int>(type: "int", nullable: false),
ReviewNote = table.Column<string>(type: "nvarchar(max)", nullable: true),
GanjoorPoemCorrectionId = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_GanjoorPoemGeoDateTagCorrection", x => x.Id);
table.ForeignKey(
name: "FK_GanjoorPoemGeoDateTagCorrection_GanjoorGeoLocations_LocationId",
column: x => x.LocationId,
principalTable: "GanjoorGeoLocations",
principalColumn: "Id");
table.ForeignKey(
name: "FK_GanjoorPoemGeoDateTagCorrection_GanjoorPoemCorrections_GanjoorPoemCorrectionId",
column: x => x.GanjoorPoemCorrectionId,
principalTable: "GanjoorPoemCorrections",
principalColumn: "Id");
table.ForeignKey(
name: "FK_GanjoorPoemGeoDateTagCorrection_GanjoorRelatedPersons_PersonId",
column: x => x.PersonId,
principalTable: "GanjoorRelatedPersons",
principalColumn: "Id");
});
migrationBuilder.CreateIndex(
name: "IX_GanjoorPoemGeoDateTagCorrection_GanjoorPoemCorrectionId",
table: "GanjoorPoemGeoDateTagCorrection",
column: "GanjoorPoemCorrectionId");
migrationBuilder.CreateIndex(
name: "IX_GanjoorPoemGeoDateTagCorrection_LocationId",
table: "GanjoorPoemGeoDateTagCorrection",
column: "LocationId");
migrationBuilder.CreateIndex(
name: "IX_GanjoorPoemGeoDateTagCorrection_PersonId",
table: "GanjoorPoemGeoDateTagCorrection",
column: "PersonId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "GanjoorPoemGeoDateTagCorrection");
}
}
}

View File

@ -1614,6 +1614,73 @@ namespace RMuseum.Migrations
b.ToTable("GanjoorPoemCorrections"); b.ToTable("GanjoorPoemCorrections");
}); });
modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int?>("CoupletIndex")
.HasColumnType("int");
b.Property<int?>("ExistingTagId")
.HasColumnType("int");
b.Property<int?>("GanjoorPoemCorrectionId")
.HasColumnType("int");
b.Property<bool>("IgnoreInCategory")
.HasColumnType("bit");
b.Property<int?>("LocationId")
.HasColumnType("int");
b.Property<int?>("LunarDay")
.HasColumnType("int");
b.Property<int?>("LunarMonth")
.HasColumnType("int");
b.Property<int?>("LunarYear")
.HasColumnType("int");
b.Property<bool>("MarkForDelete")
.HasColumnType("bit");
b.Property<int?>("PersonId")
.HasColumnType("int");
b.Property<int>("Result")
.HasColumnType("int");
b.Property<string>("ReviewNote")
.HasColumnType("nvarchar(max)");
b.Property<double?>("SuggestedLatitude")
.HasColumnType("float");
b.Property<string>("SuggestedLocationName")
.HasColumnType("nvarchar(max)");
b.Property<double?>("SuggestedLongitude")
.HasColumnType("float");
b.Property<string>("SuggestionNote")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("GanjoorPoemCorrectionId");
b.HasIndex("LocationId");
b.HasIndex("PersonId");
b.ToTable("GanjoorPoemGeoDateTagCorrection");
});
modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemProbableMetre", b => modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemProbableMetre", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -5194,6 +5261,25 @@ namespace RMuseum.Migrations
b.Navigation("User"); b.Navigation("User");
}); });
modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection", b =>
{
b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoemCorrection", null)
.WithMany("GeoDateTags")
.HasForeignKey("GanjoorPoemCorrectionId");
b.HasOne("RMuseum.Models.Ganjoor.GanjoorGeoLocation", "Location")
.WithMany()
.HasForeignKey("LocationId");
b.HasOne("RMuseum.Models.Ganjoor.GanjoorRelatedPerson", "Person")
.WithMany()
.HasForeignKey("PersonId");
b.Navigation("Location");
b.Navigation("Person");
});
modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemProbableMetre", b => modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemProbableMetre", b =>
{ {
b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoem", "Poem") b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoem", "Poem")
@ -6102,6 +6188,8 @@ namespace RMuseum.Migrations
modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemCorrection", b => modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemCorrection", b =>
{ {
b.Navigation("GeoDateTags");
b.Navigation("VerseOrderText"); b.Navigation("VerseOrderText");
}); });

View File

@ -29,6 +29,11 @@ namespace RMuseum.Models.Ganjoor
/// </summary> /// </summary>
public ICollection<GanjoorVerseVOrderText> VerseOrderText { get; set; } public ICollection<GanjoorVerseVOrderText> VerseOrderText { get; set; }
/// <summary>
/// suggested geo/date tags
/// </summary>
public ICollection<GanjoorPoemGeoDateTagCorrection> GeoDateTags { get; set; }
/// <summary> /// <summary>
/// title /// title
/// </summary> /// </summary>

View File

@ -0,0 +1,102 @@
namespace RMuseum.Models.Ganjoor
{
/// <summary>
/// suggested geo/date tag for a poem, pending moderation as part of a GanjoorPoemCorrection
/// </summary>
public class GanjoorPoemGeoDateTagCorrection
{
/// <summary>
/// record id
/// </summary>
public int Id { get; set; }
/// <summary>
/// couplet index (matches PoemGeoDateTag.CoupletIndex)
/// </summary>
public int? CoupletIndex { get; set; }
/// <summary>
/// an existing, already approved location - set this OR the Suggested* fields below, not both
/// </summary>
public int? LocationId { get; set; }
/// <summary>
/// existing location (navigation)
/// </summary>
public virtual GanjoorGeoLocation Location { get; set; }
/// <summary>
/// name for a brand new, not yet approved location suggestion
/// </summary>
public string SuggestedLocationName { get; set; }
/// <summary>
/// latitude for a brand new, not yet approved location suggestion
/// </summary>
public double? SuggestedLatitude { get; set; }
/// <summary>
/// longitude for a brand new, not yet approved location suggestion
/// </summary>
public double? SuggestedLongitude { get; set; }
/// <summary>
/// lunar year (Hijri)
/// </summary>
public int? LunarYear { get; set; }
/// <summary>
/// lunar month (Hijri)
/// </summary>
public int? LunarMonth { get; set; }
/// <summary>
/// lunar day (Hijri)
/// </summary>
public int? LunarDay { get; set; }
/// <summary>
/// related person id (existing, approved GanjoorRelatedPerson only - no suggestion path for new people yet)
/// </summary>
public int? PersonId { get; set; }
/// <summary>
/// related person (navigation)
/// </summary>
public virtual GanjoorRelatedPerson Person { get; set; }
/// <summary>
/// if true, this tag is excluded from category/poet-level map aggregation (e.g. a place mentioned only
/// for comparison, not actually visited/relevant to the poet's own path) - default false, matching
/// PoemGeoDateTag.IgnoreInCategory
/// </summary>
public bool IgnoreInCategory { get; set; }
/// <summary>
/// contributor's own reasoning for this tag - especially important for indirect date references
/// (e.g. an abjad-encoded year, or an allusion to a known historical event) where the connection
/// to the suggested location/date isn't self-evident from the tag alone
/// </summary>
public string SuggestionNote { get; set; }
/// <summary>
/// true if this suggestion is for removing an existing PoemGeoDateTag rather than adding a new one
/// </summary>
public bool MarkForDelete { get; set; }
/// <summary>
/// id of the existing PoemGeoDateTag this suggestion would remove, when MarkForDelete is true
/// </summary>
public int? ExistingTagId { get; set; }
/// <summary>
/// review result
/// </summary>
public CorrectionReviewResult Result { get; set; }
/// <summary>
/// reviewer's note
/// </summary>
public string ReviewNote { get; set; }
}
}

View File

@ -22,6 +22,11 @@ namespace RMuseum.Models.Ganjoor.ViewModels
/// </summary> /// </summary>
public GanjoorVerseVOrderText[] VerseOrderText { get; set; } public GanjoorVerseVOrderText[] VerseOrderText { get; set; }
/// <summary>
/// suggested geo/date tags
/// </summary>
public GanjoorPoemGeoDateTagCorrection[] GeoDateTags { get; set; }
/// <summary> /// <summary>
/// title /// title
/// </summary> /// </summary>

View File

@ -9247,6 +9247,11 @@
modified verses modified verses
</summary> </summary>
</member> </member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemCorrection.GeoDateTags">
<summary>
suggested geo/date tags
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemCorrection.Title"> <member name="P:RMuseum.Models.Ganjoor.GanjoorPoemCorrection.Title">
<summary> <summary>
title title
@ -9572,6 +9577,105 @@
بحر طویل بحر طویل
</summary> </summary>
</member> </member>
<member name="T:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection">
<summary>
suggested geo/date tag for a poem, pending moderation as part of a GanjoorPoemCorrection
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection.Id">
<summary>
record id
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection.CoupletIndex">
<summary>
couplet index (matches PoemGeoDateTag.CoupletIndex)
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection.LocationId">
<summary>
an existing, already approved location - set this OR the Suggested* fields below, not both
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection.Location">
<summary>
existing location (navigation)
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection.SuggestedLocationName">
<summary>
name for a brand new, not yet approved location suggestion
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection.SuggestedLatitude">
<summary>
latitude for a brand new, not yet approved location suggestion
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection.SuggestedLongitude">
<summary>
longitude for a brand new, not yet approved location suggestion
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection.LunarYear">
<summary>
lunar year (Hijri)
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection.LunarMonth">
<summary>
lunar month (Hijri)
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection.LunarDay">
<summary>
lunar day (Hijri)
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection.PersonId">
<summary>
related person id (existing, approved GanjoorRelatedPerson only - no suggestion path for new people yet)
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection.Person">
<summary>
related person (navigation)
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection.IgnoreInCategory">
<summary>
if true, this tag is excluded from category/poet-level map aggregation (e.g. a place mentioned only
for comparison, not actually visited/relevant to the poet's own path) - default false, matching
PoemGeoDateTag.IgnoreInCategory
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection.SuggestionNote">
<summary>
contributor's own reasoning for this tag - especially important for indirect date references
(e.g. an abjad-encoded year, or an allusion to a known historical event) where the connection
to the suggested location/date isn't self-evident from the tag alone
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection.MarkForDelete">
<summary>
true if this suggestion is for removing an existing PoemGeoDateTag rather than adding a new one
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection.ExistingTagId">
<summary>
id of the existing PoemGeoDateTag this suggestion would remove, when MarkForDelete is true
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection.Result">
<summary>
review result
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection.ReviewNote">
<summary>
reviewer's note
</summary>
</member>
<member name="T:RMuseum.Models.Ganjoor.GanjoorPoemProbableMetre"> <member name="T:RMuseum.Models.Ganjoor.GanjoorPoemProbableMetre">
<summary> <summary>
Probable Prosody Information for a poem Probable Prosody Information for a poem
@ -12631,6 +12735,11 @@
modified verses modified verses
</summary> </summary>
</member> </member>
<member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPoemCorrectionViewModel.GeoDateTags">
<summary>
suggested geo/date tags
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPoemCorrectionViewModel.Title"> <member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPoemCorrectionViewModel.Title">
<summary> <summary>
title title

View File

@ -2093,12 +2093,29 @@ namespace RMuseum.Services.Implementation
} }
} }
var preCorrections = await _context.GanjoorPoemCorrections.Include(c => c.VerseOrderText) var preCorrections = await _context.GanjoorPoemCorrections.Include(c => c.VerseOrderText).Include(c => c.GeoDateTags)
.Where(c => c.UserId == correction.UserId && c.PoemId == correction.PoemId && c.Reviewed == false) .Where(c => c.UserId == correction.UserId && c.PoemId == correction.PoemId && c.Reviewed == false)
.ToListAsync(); .ToListAsync();
var poem = (await GetPoemById(correction.PoemId, false, false, true, false, false, false, false, true, false)).Result; var poem = (await GetPoemById(correction.PoemId, false, false, true, false, false, false, false, true, false)).Result;
if (correction.GeoDateTags != null)
{
foreach (var geoDateTag in correction.GeoDateTags)
{
if (!geoDateTag.MarkForDelete)
{
bool hasLocation = geoDateTag.LocationId != null ||
(!string.IsNullOrWhiteSpace(geoDateTag.SuggestedLocationName) && geoDateTag.SuggestedLatitude != null && geoDateTag.SuggestedLongitude != null);
bool hasDate = geoDateTag.LunarYear != null;
if (!hasLocation && !hasDate)
{
return new RServiceResult<GanjoorPoemCorrectionViewModel>(null, "برچسب جغرافیایی/تاریخی باید حداقل شامل مکان یا تاریخ باشد.");
}
}
}
}
foreach (var verse in correction.VerseOrderText) foreach (var verse in correction.VerseOrderText)
{ {
if (!verse.NewVerse) if (!verse.NewVerse)
@ -2133,6 +2150,7 @@ namespace RMuseum.Services.Implementation
//Language = correction.Language, not used //Language = correction.Language, not used
PoemSummary = correction.PoemSummary, PoemSummary = correction.PoemSummary,
HideMyName = correction.HideMyName, HideMyName = correction.HideMyName,
GeoDateTags = correction.GeoDateTags,
}; };
_context.GanjoorPoemCorrections.Add(dbCorrection); _context.GanjoorPoemCorrections.Add(dbCorrection);
@ -2144,6 +2162,7 @@ namespace RMuseum.Services.Implementation
foreach (var preCorrection in preCorrections) foreach (var preCorrection in preCorrections)
{ {
preCorrection.VerseOrderText.Clear(); preCorrection.VerseOrderText.Clear();
preCorrection.GeoDateTags?.Clear();
} }
_context.GanjoorPoemCorrections.RemoveRange(preCorrections); _context.GanjoorPoemCorrections.RemoveRange(preCorrections);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();