diff --git a/backend/src/Core/MathComps.Domain/EfCoreEntities/ProblemText.cs b/backend/src/Core/MathComps.Domain/EfCoreEntities/ProblemText.cs index b1f6106d..85ea465f 100644 --- a/backend/src/Core/MathComps.Domain/EfCoreEntities/ProblemText.cs +++ b/backend/src/Core/MathComps.Domain/EfCoreEntities/ProblemText.cs @@ -41,6 +41,12 @@ public class ProblemText [Column(TypeName = "jsonb")] public string? ParsedText { get; set; } + /// + /// The text content rendered to Markdown+TeX. Null on rows not yet migrated from the JSON AST. + /// + [Column(TypeName = "text")] + public string? MarkdownText { get; set; } + /// /// The language of this text. /// diff --git a/backend/src/Infrastructure/MathComps.Infrastructure/Migrations/20260510145752_AddMarkdownTextToProblemText.Designer.cs b/backend/src/Infrastructure/MathComps.Infrastructure/Migrations/20260510145752_AddMarkdownTextToProblemText.Designer.cs new file mode 100644 index 00000000..42e503c2 --- /dev/null +++ b/backend/src/Infrastructure/MathComps.Infrastructure/Migrations/20260510145752_AddMarkdownTextToProblemText.Designer.cs @@ -0,0 +1,1406 @@ +// +using System; +using MathComps.Domain.EfCoreEntities; +using MathComps.Infrastructure.Persistence; +using MathComps.Shared; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Pgvector; + +#nullable disable + +namespace MathComps.Infrastructure.Migrations +{ + [DbContext(typeof(MathCompsDbContext))] + [Migration("20260510145752_AddMarkdownTextToProblemText")] + partial class AddMarkdownTextToProblemText + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "comment_status", new[] { "active", "deleted", "superseded" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "document_type", new[] { "solution", "statement" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "language", new[] { "cs", "en", "sk" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "tag_type", new[] { "area", "goal", "technique", "type" }); + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "pg_trgm"); + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "unaccent"); + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "vector"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Author", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("name"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("slug"); + + b.HasKey("Id") + .HasName("pk_authors"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ux_author_slug"); + + b.ToTable("authors", (string)null); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Category", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("slug"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasColumnName("sort_order"); + + b.HasKey("Id") + .HasName("pk_categories"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ux_category_slug"); + + b.HasIndex("SortOrder") + .IsUnique() + .HasDatabaseName("ux_category_sort_order"); + + b.ToTable("categories", null, t => + { + t.HasCheckConstraint("ck_category_sort_order_positive", "\"sort_order\" > 0"); + }); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Comment", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AuthorId") + .HasColumnType("uuid") + .HasColumnName("author_id"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasColumnName("content"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ParentCommentId") + .HasColumnType("uuid") + .HasColumnName("parent_comment_id"); + + b.Property("PreviousVersionId") + .HasColumnType("uuid") + .HasColumnName("previous_version_id"); + + b.Property("Status") + .HasColumnType("comment_status") + .HasColumnName("status"); + + b.HasKey("Id") + .HasName("pk_comments"); + + b.HasIndex("AuthorId") + .HasDatabaseName("ix_comment_author_id"); + + b.HasIndex("ParentCommentId") + .HasDatabaseName("ix_comment_parent_id"); + + b.HasIndex("PreviousVersionId") + .HasDatabaseName("ix_comments_previous_version_id"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.CommentLike", b => + { + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("CommentId") + .HasColumnType("uuid") + .HasColumnName("comment_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.HasKey("UserId", "CommentId") + .HasName("pk_comment_likes"); + + b.HasIndex("CommentId") + .HasDatabaseName("ix_comment_like_comment_id"); + + b.ToTable("comment_likes", (string)null); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Competition", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("slug"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasColumnName("sort_order"); + + b.HasKey("Id") + .HasName("pk_competitions"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ux_competition_slug"); + + b.HasIndex("SortOrder") + .IsUnique() + .HasDatabaseName("ux_competition_sort_order"); + + b.ToTable("competitions", null, t => + { + t.HasCheckConstraint("ck_competition_sort_order_positive", "\"sort_order\" > 0"); + }); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Handout", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContentId") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)") + .HasColumnName("content_id"); + + b.HasKey("Id") + .HasName("pk_handouts"); + + b.HasIndex("ContentId") + .IsUnique() + .HasDatabaseName("ux_handout_content_id"); + + b.ToTable("handouts", (string)null); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.HandoutComment", b => + { + b.Property("HandoutId") + .HasColumnType("uuid") + .HasColumnName("handout_id"); + + b.Property("CommentId") + .HasColumnType("uuid") + .HasColumnName("comment_id"); + + b.HasKey("HandoutId", "CommentId") + .HasName("pk_handout_comments"); + + b.HasIndex("CommentId") + .IsUnique() + .HasDatabaseName("ux_handout_comment_comment_id"); + + b.ToTable("handout_comments", (string)null); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.NewsArticle", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContentId") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)") + .HasColumnName("content_id"); + + b.HasKey("Id") + .HasName("pk_news_articles"); + + b.HasIndex("ContentId") + .IsUnique() + .HasDatabaseName("ux_news_article_content_id"); + + b.ToTable("news_articles", (string)null); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.NewsArticleComment", b => + { + b.Property("NewsArticleId") + .HasColumnType("uuid") + .HasColumnName("news_article_id"); + + b.Property("CommentId") + .HasColumnType("uuid") + .HasColumnName("comment_id"); + + b.HasKey("NewsArticleId", "CommentId") + .HasName("pk_news_article_comments"); + + b.HasIndex("CommentId") + .IsUnique() + .HasDatabaseName("ux_news_article_comment_comment_id"); + + b.ToTable("news_article_comments", (string)null); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Problem", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("Number") + .HasColumnType("integer") + .HasColumnName("number"); + + b.Property("RoundInstanceId") + .HasColumnType("uuid") + .HasColumnName("round_instance_id"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("slug"); + + b.Property("SolutionLink") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("solution_link"); + + b.HasKey("Id") + .HasName("pk_problems"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_problems_category_id"); + + b.HasIndex("RoundInstanceId", "Number") + .IsUnique() + .HasDatabaseName("ux_problem_round_instance_number"); + + b.ToTable("problems", null, t => + { + t.HasCheckConstraint("ck_problem_number_positive", "\"number\" > 0"); + }); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemAuthor", b => + { + b.Property("ProblemId") + .HasColumnType("uuid") + .HasColumnName("problem_id"); + + b.Property("AuthorId") + .HasColumnType("uuid") + .HasColumnName("author_id"); + + b.Property("Ordinal") + .HasColumnType("integer") + .HasColumnName("ordinal"); + + b.HasKey("ProblemId", "AuthorId") + .HasName("pk_problem_authors"); + + b.HasIndex("AuthorId") + .HasDatabaseName("ix_problem_author_author_id"); + + b.HasIndex("ProblemId", "Ordinal") + .IsUnique() + .HasDatabaseName("ux_problem_author_problem_ordinal"); + + b.ToTable("problem_authors", null, t => + { + t.HasCheckConstraint("ck_problem_author_order_positive", "\"ordinal\" > 0"); + }); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemComment", b => + { + b.Property("ProblemId") + .HasColumnType("uuid") + .HasColumnName("problem_id"); + + b.Property("CommentId") + .HasColumnType("uuid") + .HasColumnName("comment_id"); + + b.HasKey("ProblemId", "CommentId") + .HasName("pk_problem_comments"); + + b.HasIndex("CommentId") + .IsUnique() + .HasDatabaseName("ux_problem_comment_comment_id"); + + b.ToTable("problem_comments", (string)null); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemEmbedding", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("DateUpdated") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_updated"); + + b.Property("Embedding") + .IsRequired() + .HasColumnType("vector(1536)") + .HasColumnName("embedding"); + + b.Property("EmbeddingType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("embedding_type"); + + b.Property("ModelName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("model_name"); + + b.Property("ProblemTextId") + .HasColumnType("uuid") + .HasColumnName("problem_text_id"); + + b.HasKey("Id") + .HasName("pk_problem_embeddings"); + + b.HasIndex("Embedding") + .HasDatabaseName("ix_problem_embedding_cosine") + .HasAnnotation("Npgsql:StorageParameter:lists", 100); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Embedding"), "ivfflat"); + NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex("Embedding"), new[] { "vector_cosine_ops" }); + + b.HasIndex("ProblemTextId") + .HasDatabaseName("ix_problem_embedding_problem_text_id"); + + b.HasIndex("ProblemTextId", "EmbeddingType", "ModelName") + .IsUnique() + .HasDatabaseName("ux_problem_embedding_text_embedding_model"); + + b.ToTable("problem_embeddings", (string)null); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemImage", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContentId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("content_id"); + + b.Property("Height") + .IsRequired() + .HasColumnType("text") + .HasColumnName("height"); + + b.Property("OriginalId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("original_id"); + + b.Property("ProblemId") + .HasColumnType("uuid") + .HasColumnName("problem_id"); + + b.Property("Scale") + .HasColumnType("numeric") + .HasColumnName("scale"); + + b.Property("Width") + .IsRequired() + .HasColumnType("text") + .HasColumnName("width"); + + b.HasKey("Id") + .HasName("pk_problem_images"); + + b.HasIndex("ProblemId", "ContentId") + .IsUnique() + .HasDatabaseName("ux_problem_image_problem_content_id"); + + b.ToTable("problem_images", (string)null); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemLike", b => + { + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("ProblemId") + .HasColumnType("uuid") + .HasColumnName("problem_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.HasKey("UserId", "ProblemId") + .HasName("pk_problem_likes"); + + b.HasIndex("ProblemId") + .HasDatabaseName("ix_problem_like_problem_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_problem_like_user_id"); + + b.ToTable("problem_likes", (string)null); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemMarkStatus", b => + { + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("ProblemId") + .HasColumnType("uuid") + .HasColumnName("problem_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.HasKey("UserId", "ProblemId") + .HasName("pk_problem_mark_statuses"); + + b.HasIndex("ProblemId") + .HasDatabaseName("ix_problem_mark_status_problem_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_problem_mark_status_user_id"); + + b.ToTable("problem_mark_statuses", (string)null); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemSimilarity", b => + { + b.Property("SourceProblemId") + .HasColumnType("uuid") + .HasColumnName("source_problem_id"); + + b.Property("SimilarProblemId") + .HasColumnType("uuid") + .HasColumnName("similar_problem_id"); + + b.Property("SimilarityScore") + .HasColumnType("double precision") + .HasColumnName("similarity_score"); + + b.HasKey("SourceProblemId", "SimilarProblemId") + .HasName("pk_problem_similarities"); + + b.HasIndex("SimilarProblemId") + .HasDatabaseName("ix_problem_similarity_similar_problem_id"); + + b.ToTable("problem_similarities", null, t => + { + t.HasCheckConstraint("ck_problem_similarity_not_self", "\"source_problem_id\" <> \"similar_problem_id\""); + }); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemTag", b => + { + b.Property("ProblemId") + .HasColumnType("uuid") + .HasColumnName("problem_id"); + + b.Property("TagId") + .HasColumnType("uuid") + .HasColumnName("tag_id"); + + b.Property("Confidence") + .HasColumnType("integer") + .HasColumnName("confidence"); + + b.Property("GoodnessOfFit") + .HasColumnType("real") + .HasColumnName("goodness_of_fit"); + + b.Property("Justification") + .HasColumnType("text") + .HasColumnName("justification"); + + b.HasKey("ProblemId", "TagId") + .HasName("pk_problem_tag"); + + b.HasIndex("ProblemId") + .HasDatabaseName("ix_problem_tag_problem_id"); + + b.HasIndex("TagId") + .HasDatabaseName("ix_problem_tag_tag_id"); + + b.ToTable("problem_tag", (string)null); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemText", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_modified"); + + b.Property("DocumentType") + .HasColumnType("document_type") + .HasColumnName("document_type"); + + b.Property("IsOriginal") + .HasColumnType("boolean") + .HasColumnName("is_original"); + + b.Property("Language") + .HasColumnType("language") + .HasColumnName("language"); + + b.Property("MarkdownText") + .HasColumnType("text") + .HasColumnName("markdown_text"); + + b.Property("ParsedText") + .HasColumnType("jsonb") + .HasColumnName("parsed_text"); + + b.Property("ProblemId") + .HasColumnType("uuid") + .HasColumnName("problem_id"); + + b.Property("RawText") + .IsRequired() + .HasColumnType("text") + .HasColumnName("raw_text"); + + b.HasKey("Id") + .HasName("pk_problem_texts"); + + b.HasIndex("ProblemId") + .HasDatabaseName("ix_problem_text_problem_id"); + + b.HasIndex("RawText") + .HasDatabaseName("ix_problem_text_raw_text_unaccent_trgm") + .HasFilter("raw_text IS NOT NULL") + .HasAnnotation("Npgsql:IndexExpression", "immutable_unaccent(raw_text)"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("RawText"), "gin"); + NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex("RawText"), new[] { "gin_trgm_ops" }); + + b.HasIndex("ProblemId", "DocumentType") + .IsUnique() + .HasDatabaseName("ux_problem_text_one_original_per_problem_document") + .HasFilter("is_original = true"); + + b.HasIndex("ProblemId", "DocumentType", "Language") + .IsUnique() + .HasDatabaseName("ux_problem_text_problem_document_language"); + + b.HasIndex("ProblemId", "Language", "IsOriginal") + .HasDatabaseName("ix_problem_text_statement_lookup") + .HasFilter("document_type = 'statement' AND parsed_text IS NOT NULL"); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("ProblemId", "Language", "IsOriginal"), new[] { "ParsedText" }); + + b.ToTable("problem_texts", (string)null); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Round", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CompetitionId") + .HasColumnType("uuid") + .HasColumnName("competition_id"); + + b.Property("CompositeSlug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("composite_slug"); + + b.Property("IsDefault") + .HasColumnType("boolean") + .HasColumnName("is_default"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("slug"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasColumnName("sort_order"); + + b.HasKey("Id") + .HasName("pk_rounds"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_rounds_category_id"); + + b.HasIndex("CompositeSlug") + .IsUnique() + .HasDatabaseName("ux_round_composite_slug"); + + b.HasIndex("CompetitionId", "SortOrder") + .IsUnique() + .HasDatabaseName("ux_round_competition_category_sort_order_when_category_null") + .HasFilter("\"category_id\" IS NULL"); + + b.HasIndex("CompetitionId", "CategoryId", "Slug") + .IsUnique() + .HasDatabaseName("ux_round_competition_category_slug"); + + b.HasIndex("CompetitionId", "CategoryId", "SortOrder") + .IsUnique() + .HasDatabaseName("ux_round_competition_category_sort_order_when_category_not_null") + .HasFilter("\"category_id\" IS NOT NULL"); + + b.ToTable("rounds", null, t => + { + t.HasCheckConstraint("ck_round_sort_order_positive", "\"sort_order\" > 0"); + }); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.RoundInstance", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Date") + .HasColumnType("date") + .HasColumnName("date"); + + b.Property("RoundId") + .HasColumnType("uuid") + .HasColumnName("round_id"); + + b.Property("SeasonId") + .HasColumnType("uuid") + .HasColumnName("season_id"); + + b.HasKey("Id") + .HasName("pk_round_instances"); + + b.HasIndex("SeasonId") + .HasDatabaseName("ix_round_instances_season_id"); + + b.HasIndex("RoundId", "SeasonId") + .IsUnique() + .HasDatabaseName("ux_round_instance_round_season"); + + b.ToTable("round_instances", (string)null); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Season", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("EditionNumber") + .HasColumnType("integer") + .HasColumnName("edition_number"); + + b.Property("StartYear") + .HasColumnType("integer") + .HasColumnName("start_year"); + + b.HasKey("Id") + .HasName("pk_seasons"); + + b.HasIndex("EditionNumber") + .IsUnique() + .HasDatabaseName("ux_season_edition_number"); + + b.HasIndex("StartYear") + .IsUnique() + .HasDatabaseName("ux_season_start_year"); + + b.ToTable("seasons", null, t => + { + t.HasCheckConstraint("ck_season_edition_positive", "\"edition_number\" > 0"); + + t.HasCheckConstraint("ck_season_start_year_sane", "\"start_year\" >= 1900"); + }); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Tag", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("slug"); + + b.Property("TagType") + .HasColumnType("tag_type") + .HasColumnName("tag_type"); + + b.HasKey("Id") + .HasName("pk_tags"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ux_tag_slug"); + + b.ToTable("tags", (string)null); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.User", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("avatar_url"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("display_name"); + + b.Property("Email") + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("email"); + + b.Property("ExternalId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("external_id"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.HasIndex("ExternalId") + .IsUnique() + .HasDatabaseName("ux_user_external_id"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.UserProblemList", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContentId") + .IsRequired() + .HasMaxLength(21) + .HasColumnType("character varying(21)") + .HasColumnName("content_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("IsShared") + .HasColumnType("boolean") + .HasColumnName("is_shared"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("name"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasColumnName("sort_order"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_user_problem_lists"); + + b.HasIndex("ContentId") + .IsUnique() + .HasDatabaseName("ux_user_problem_list_content_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_user_problem_list_user_id"); + + b.ToTable("user_problem_lists", null, t => + { + t.HasCheckConstraint("ck_user_problem_list_sort_order_positive", "\"sort_order\" > 0"); + }); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.UserProblemListItem", b => + { + b.Property("ListId") + .HasColumnType("uuid") + .HasColumnName("list_id"); + + b.Property("ProblemId") + .HasColumnType("uuid") + .HasColumnName("problem_id"); + + b.Property("AddedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("added_at"); + + b.HasKey("ListId", "ProblemId") + .HasName("pk_user_problem_list_items"); + + b.HasIndex("ProblemId") + .HasDatabaseName("ix_user_problem_list_item_problem_id"); + + b.ToTable("user_problem_list_items", (string)null); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Comment", b => + { + b.HasOne("MathComps.Domain.EfCoreEntities.User", "Author") + .WithMany() + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_comments_users_author_id"); + + b.HasOne("MathComps.Domain.EfCoreEntities.Comment", "ParentComment") + .WithMany("Replies") + .HasForeignKey("ParentCommentId") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_comments_comments_parent_comment_id"); + + b.HasOne("MathComps.Domain.EfCoreEntities.Comment", "PreviousVersion") + .WithMany() + .HasForeignKey("PreviousVersionId") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_comments_comments_previous_version_id"); + + b.Navigation("Author"); + + b.Navigation("ParentComment"); + + b.Navigation("PreviousVersion"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.CommentLike", b => + { + b.HasOne("MathComps.Domain.EfCoreEntities.Comment", "Comment") + .WithMany("Likes") + .HasForeignKey("CommentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_comment_likes_comments_comment_id"); + + b.HasOne("MathComps.Domain.EfCoreEntities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_comment_likes_users_user_id"); + + b.Navigation("Comment"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.HandoutComment", b => + { + b.HasOne("MathComps.Domain.EfCoreEntities.Comment", "Comment") + .WithMany() + .HasForeignKey("CommentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_handout_comments_comments_comment_id"); + + b.HasOne("MathComps.Domain.EfCoreEntities.Handout", "Handout") + .WithMany("Comments") + .HasForeignKey("HandoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_handout_comments_handouts_handout_id"); + + b.Navigation("Comment"); + + b.Navigation("Handout"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.NewsArticleComment", b => + { + b.HasOne("MathComps.Domain.EfCoreEntities.Comment", "Comment") + .WithMany() + .HasForeignKey("CommentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_news_article_comments_comments_comment_id"); + + b.HasOne("MathComps.Domain.EfCoreEntities.NewsArticle", "NewsArticle") + .WithMany("Comments") + .HasForeignKey("NewsArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_news_article_comments_news_articles_news_article_id"); + + b.Navigation("Comment"); + + b.Navigation("NewsArticle"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Problem", b => + { + b.HasOne("MathComps.Domain.EfCoreEntities.Category", null) + .WithMany("Problems") + .HasForeignKey("CategoryId") + .HasConstraintName("fk_problems_categories_category_id"); + + b.HasOne("MathComps.Domain.EfCoreEntities.RoundInstance", "RoundInstance") + .WithMany("Problems") + .HasForeignKey("RoundInstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_problems_round_instances_round_instance_id"); + + b.Navigation("RoundInstance"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemAuthor", b => + { + b.HasOne("MathComps.Domain.EfCoreEntities.Author", "Author") + .WithMany("ProblemAuthors") + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_problem_authors_authors_author_id"); + + b.HasOne("MathComps.Domain.EfCoreEntities.Problem", "Problem") + .WithMany("ProblemAuthors") + .HasForeignKey("ProblemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_problem_authors_problems_problem_id"); + + b.Navigation("Author"); + + b.Navigation("Problem"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemComment", b => + { + b.HasOne("MathComps.Domain.EfCoreEntities.Comment", "Comment") + .WithMany() + .HasForeignKey("CommentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_problem_comments_comments_comment_id"); + + b.HasOne("MathComps.Domain.EfCoreEntities.Problem", "Problem") + .WithMany("ProblemComments") + .HasForeignKey("ProblemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_problem_comments_problems_problem_id"); + + b.Navigation("Comment"); + + b.Navigation("Problem"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemEmbedding", b => + { + b.HasOne("MathComps.Domain.EfCoreEntities.ProblemText", "ProblemText") + .WithMany("Embeddings") + .HasForeignKey("ProblemTextId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_problem_embeddings_problem_texts_problem_text_id"); + + b.Navigation("ProblemText"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemImage", b => + { + b.HasOne("MathComps.Domain.EfCoreEntities.Problem", "Problem") + .WithMany("Images") + .HasForeignKey("ProblemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_problem_images_problems_problem_id"); + + b.Navigation("Problem"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemLike", b => + { + b.HasOne("MathComps.Domain.EfCoreEntities.Problem", "Problem") + .WithMany("Likes") + .HasForeignKey("ProblemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_problem_likes_problems_problem_id"); + + b.HasOne("MathComps.Domain.EfCoreEntities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_problem_likes_users_user_id"); + + b.Navigation("Problem"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemMarkStatus", b => + { + b.HasOne("MathComps.Domain.EfCoreEntities.Problem", "Problem") + .WithMany("MarkStatuses") + .HasForeignKey("ProblemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_problem_mark_statuses_problems_problem_id"); + + b.HasOne("MathComps.Domain.EfCoreEntities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_problem_mark_statuses_users_user_id"); + + b.Navigation("Problem"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemSimilarity", b => + { + b.HasOne("MathComps.Domain.EfCoreEntities.Problem", "SimilarProblem") + .WithMany("AppearsInProblems") + .HasForeignKey("SimilarProblemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_problem_similarities_problems_similar_problem_id"); + + b.HasOne("MathComps.Domain.EfCoreEntities.Problem", "SourceProblem") + .WithMany("SimilarProblems") + .HasForeignKey("SourceProblemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_problem_similarities_problems_source_problem_id"); + + b.OwnsOne("MathComps.Domain.SimilarityComponents", "Components", b1 => + { + b1.Property("ProblemSimilaritySourceProblemId") + .HasColumnType("uuid"); + + b1.Property("ProblemSimilaritySimilarProblemId") + .HasColumnType("uuid"); + + b1.Property("CompetitionSimilarity") + .HasColumnType("double precision"); + + b1.Property("SolutionSimilarity") + .HasColumnType("double precision"); + + b1.Property("StatementSimilarity") + .HasColumnType("double precision"); + + b1.Property("TagSimilarity") + .HasColumnType("double precision"); + + b1.HasKey("ProblemSimilaritySourceProblemId", "ProblemSimilaritySimilarProblemId"); + + b1.ToTable("problem_similarities"); + + b1.ToJson("components"); + + b1.WithOwner() + .HasForeignKey("ProblemSimilaritySourceProblemId", "ProblemSimilaritySimilarProblemId") + .HasConstraintName("fk_problem_similarities_problem_similarities_source_problem_id"); + }); + + b.Navigation("Components"); + + b.Navigation("SimilarProblem"); + + b.Navigation("SourceProblem"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemTag", b => + { + b.HasOne("MathComps.Domain.EfCoreEntities.Problem", "Problem") + .WithMany("ProblemTagsAll") + .HasForeignKey("ProblemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_problem_tag_problems_problem_id"); + + b.HasOne("MathComps.Domain.EfCoreEntities.Tag", "Tag") + .WithMany("ProblemTagsAll") + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_problem_tag_tags_tag_id"); + + b.Navigation("Problem"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemText", b => + { + b.HasOne("MathComps.Domain.EfCoreEntities.Problem", "Problem") + .WithMany("Texts") + .HasForeignKey("ProblemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_problem_texts_problems_problem_id"); + + b.Navigation("Problem"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Round", b => + { + b.HasOne("MathComps.Domain.EfCoreEntities.Category", "Category") + .WithMany("Rounds") + .HasForeignKey("CategoryId") + .HasConstraintName("fk_rounds_categories_category_id"); + + b.HasOne("MathComps.Domain.EfCoreEntities.Competition", "Competition") + .WithMany("Rounds") + .HasForeignKey("CompetitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_rounds_competitions_competition_id"); + + b.Navigation("Category"); + + b.Navigation("Competition"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.RoundInstance", b => + { + b.HasOne("MathComps.Domain.EfCoreEntities.Round", "Round") + .WithMany("RoundInstances") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_round_instances_rounds_round_id"); + + b.HasOne("MathComps.Domain.EfCoreEntities.Season", "Season") + .WithMany("RoundInstances") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_round_instances_seasons_season_id"); + + b.Navigation("Round"); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.UserProblemList", b => + { + b.HasOne("MathComps.Domain.EfCoreEntities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_problem_lists_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.UserProblemListItem", b => + { + b.HasOne("MathComps.Domain.EfCoreEntities.UserProblemList", "List") + .WithMany("Items") + .HasForeignKey("ListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_problem_list_items_user_problem_lists_list_id"); + + b.HasOne("MathComps.Domain.EfCoreEntities.Problem", "Problem") + .WithMany("UserProblemListItems") + .HasForeignKey("ProblemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_user_problem_list_items_problems_problem_id"); + + b.Navigation("List"); + + b.Navigation("Problem"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Author", b => + { + b.Navigation("ProblemAuthors"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Category", b => + { + b.Navigation("Problems"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Comment", b => + { + b.Navigation("Likes"); + + b.Navigation("Replies"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Competition", b => + { + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Handout", b => + { + b.Navigation("Comments"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.NewsArticle", b => + { + b.Navigation("Comments"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Problem", b => + { + b.Navigation("AppearsInProblems"); + + b.Navigation("Images"); + + b.Navigation("Likes"); + + b.Navigation("MarkStatuses"); + + b.Navigation("ProblemAuthors"); + + b.Navigation("ProblemComments"); + + b.Navigation("ProblemTagsAll"); + + b.Navigation("SimilarProblems"); + + b.Navigation("Texts"); + + b.Navigation("UserProblemListItems"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.ProblemText", b => + { + b.Navigation("Embeddings"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Round", b => + { + b.Navigation("RoundInstances"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.RoundInstance", b => + { + b.Navigation("Problems"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Season", b => + { + b.Navigation("RoundInstances"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.Tag", b => + { + b.Navigation("ProblemTagsAll"); + }); + + modelBuilder.Entity("MathComps.Domain.EfCoreEntities.UserProblemList", b => + { + b.Navigation("Items"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/Infrastructure/MathComps.Infrastructure/Migrations/20260510145752_AddMarkdownTextToProblemText.cs b/backend/src/Infrastructure/MathComps.Infrastructure/Migrations/20260510145752_AddMarkdownTextToProblemText.cs new file mode 100644 index 00000000..b425beca --- /dev/null +++ b/backend/src/Infrastructure/MathComps.Infrastructure/Migrations/20260510145752_AddMarkdownTextToProblemText.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MathComps.Infrastructure.Migrations +{ + /// + public partial class AddMarkdownTextToProblemText : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "markdown_text", + table: "problem_texts", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "markdown_text", + table: "problem_texts"); + } + } +} diff --git a/backend/src/Infrastructure/MathComps.Infrastructure/Migrations/MathCompsDbContextModelSnapshot.cs b/backend/src/Infrastructure/MathComps.Infrastructure/Migrations/MathCompsDbContextModelSnapshot.cs index ceee7b7a..654e42f9 100644 --- a/backend/src/Infrastructure/MathComps.Infrastructure/Migrations/MathCompsDbContextModelSnapshot.cs +++ b/backend/src/Infrastructure/MathComps.Infrastructure/Migrations/MathCompsDbContextModelSnapshot.cs @@ -605,6 +605,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("language") .HasColumnName("language"); + b.Property("MarkdownText") + .HasColumnType("text") + .HasColumnName("markdown_text"); + b.Property("ParsedText") .HasColumnType("jsonb") .HasColumnName("parsed_text");