Add per-user Like (پسندیدن) with a liked page and counts
CI/CD / CI · dotnet build (push) Successful in 2m54s
CI/CD / Deploy · hamkadr (push) Successful in 2m48s

Logged-in users can like a listing (job/shift/talent); dislike is removed per request — only likes.
- Like model (polymorphic by TargetType+TargetId) + EF migration; unique per (user, listing).
- POST /like toggles the like (auth required) and returns {liked, count}.
- Detail pages: the old ♡ Save / ✕ Dismiss buttons are replaced by a single heart Like button that
  shows the live count and toggles in place; clicking while logged out redirects to login.
- New «❤️ پسندیده‌ها» page (/Me/Liked) lists everything the user liked (open listings only), with a
  nav entry shown only when authenticated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-23 12:25:10 +03:30
parent 39c866f4c7
commit c1c914df9f
16 changed files with 2002 additions and 24 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,56 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace JobsMedical.Web.Migrations
{
/// <inheritdoc />
public partial class Likes : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Likes",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
UserId = table.Column<int>(type: "integer", nullable: false),
TargetType = table.Column<int>(type: "integer", nullable: false),
TargetId = table.Column<int>(type: "integer", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Likes", x => x.Id);
table.ForeignKey(
name: "FK_Likes_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Likes_TargetType_TargetId",
table: "Likes",
columns: new[] { "TargetType", "TargetId" });
migrationBuilder.CreateIndex(
name: "IX_Likes_UserId_TargetType_TargetId",
table: "Likes",
columns: new[] { "UserId", "TargetType", "TargetId" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Likes");
}
}
}
@@ -727,6 +727,36 @@ namespace JobsMedical.Web.Migrations
b.ToTable("JobOpenings");
});
modelBuilder.Entity("JobsMedical.Web.Models.Like", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("TargetId")
.HasColumnType("integer");
b.Property<int>("TargetType")
.HasColumnType("integer");
b.Property<int>("UserId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("TargetType", "TargetId");
b.HasIndex("UserId", "TargetType", "TargetId")
.IsUnique();
b.ToTable("Likes");
});
modelBuilder.Entity("JobsMedical.Web.Models.Notification", b =>
{
b.Property<long>("Id")
@@ -1467,6 +1497,17 @@ namespace JobsMedical.Web.Migrations
b.Navigation("Role");
});
modelBuilder.Entity("JobsMedical.Web.Models.Like", b =>
{
b.HasOne("JobsMedical.Web.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("JobsMedical.Web.Models.Notification", b =>
{
b.HasOne("JobsMedical.Web.Models.User", "User")