Social auto-posting (phase 1): daily applicant digest to Telegram/Bale + Instagram caption
CI/CD / CI · dotnet build (push) Successful in 1m51s
CI/CD / Deploy · hamkadr (push) Successful in 2m51s

Adds a «شبکه‌های اجتماعی» admin section + scheduler that publishes a daily
«کادر آماده‌به‌کار امروز» digest:

- AppSetting: social toggles, posts-per-day, editable header/footer,
  per-channel bot token + chat id (Telegram, Bale), Instagram enable +
  extra hashtags, proxy toggle, last-posted timestamp (+ migration).
- SocialPostService: builds today's talent digest as text, posts to
  Telegram and Bale via their bot sendMessage APIs (proxy-aware), and
  produces an Instagram caption + auto hashtags (role/city based).
- SocialPostWorker: posts N times/day, evenly spaced, self-paced; reads
  settings live so it's togglable without redeploy.
- /Admin/Social: credentials + header/footer + posts/day, live preview of
  today's message, «ارسال اکنون» button, and an Instagram caption pack
  with copy button (semi-automatic — you post the image manually).
- Nav link added.

Telegram/Bale post as TEXT (per request). The Vazirmatn image card for
Instagram is phase 2 (needs SkiaSharp+HarfBuzz + a TTF font).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-08 09:20:49 +03:30
parent 2bb8771ade
commit fb02c81830
10 changed files with 2186 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,172 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace JobsMedical.Web.Migrations
{
/// <inheritdoc />
public partial class SocialPosting : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "InstagramHashtags",
table: "AppSettings",
type: "character varying(1000)",
maxLength: 1000,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "SocialBaleBotToken",
table: "AppSettings",
type: "character varying(200)",
maxLength: 200,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "SocialBaleChatId",
table: "AppSettings",
type: "character varying(120)",
maxLength: 120,
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "SocialBaleEnabled",
table: "AppSettings",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "SocialEnabled",
table: "AppSettings",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "SocialFooter",
table: "AppSettings",
type: "character varying(1000)",
maxLength: 1000,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "SocialHeader",
table: "AppSettings",
type: "character varying(1000)",
maxLength: 1000,
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "SocialInstagramEnabled",
table: "AppSettings",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<DateTime>(
name: "SocialLastPostedAt",
table: "AppSettings",
type: "timestamp with time zone",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "SocialPostsPerDay",
table: "AppSettings",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<string>(
name: "SocialTelegramBotToken",
table: "AppSettings",
type: "character varying(200)",
maxLength: 200,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "SocialTelegramChatId",
table: "AppSettings",
type: "character varying(120)",
maxLength: 120,
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "SocialTelegramEnabled",
table: "AppSettings",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "SocialUseProxy",
table: "AppSettings",
type: "boolean",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "InstagramHashtags",
table: "AppSettings");
migrationBuilder.DropColumn(
name: "SocialBaleBotToken",
table: "AppSettings");
migrationBuilder.DropColumn(
name: "SocialBaleChatId",
table: "AppSettings");
migrationBuilder.DropColumn(
name: "SocialBaleEnabled",
table: "AppSettings");
migrationBuilder.DropColumn(
name: "SocialEnabled",
table: "AppSettings");
migrationBuilder.DropColumn(
name: "SocialFooter",
table: "AppSettings");
migrationBuilder.DropColumn(
name: "SocialHeader",
table: "AppSettings");
migrationBuilder.DropColumn(
name: "SocialInstagramEnabled",
table: "AppSettings");
migrationBuilder.DropColumn(
name: "SocialLastPostedAt",
table: "AppSettings");
migrationBuilder.DropColumn(
name: "SocialPostsPerDay",
table: "AppSettings");
migrationBuilder.DropColumn(
name: "SocialTelegramBotToken",
table: "AppSettings");
migrationBuilder.DropColumn(
name: "SocialTelegramChatId",
table: "AppSettings");
migrationBuilder.DropColumn(
name: "SocialTelegramEnabled",
table: "AppSettings");
migrationBuilder.DropColumn(
name: "SocialUseProxy",
table: "AppSettings");
}
}
}
@@ -99,6 +99,10 @@ namespace JobsMedical.Web.Migrations
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<string>("InstagramHashtags")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)");
b.Property<bool>("MedjobsEnabled")
.HasColumnType("boolean");
@@ -133,6 +137,51 @@ namespace JobsMedical.Web.Migrations
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("SocialBaleBotToken")
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<string>("SocialBaleChatId")
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<bool>("SocialBaleEnabled")
.HasColumnType("boolean");
b.Property<bool>("SocialEnabled")
.HasColumnType("boolean");
b.Property<string>("SocialFooter")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)");
b.Property<string>("SocialHeader")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)");
b.Property<bool>("SocialInstagramEnabled")
.HasColumnType("boolean");
b.Property<DateTime?>("SocialLastPostedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("SocialPostsPerDay")
.HasColumnType("integer");
b.Property<string>("SocialTelegramBotToken")
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<string>("SocialTelegramChatId")
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<bool>("SocialTelegramEnabled")
.HasColumnType("boolean");
b.Property<bool>("SocialUseProxy")
.HasColumnType("boolean");
b.Property<string>("TelegramChannels")
.HasMaxLength(2000)
.HasColumnType("character varying(2000)");