Files
arsam e9cd12dda6 Import remaining project documentation and reference notes
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:33:04 +03:30

36 KiB
Raw Permalink Blame History

Phase 9 — Content Modernization, Publication Features & SEO Enrichment

Status: Implemented; Linux validation pending

Implemented in this pass:

  • Additive Prisma models/fields for SEO metadata, article view logs, newsletter campaigns, article series, editorial reviews, comment likes, and reading lists.
  • Backend APIs for related articles, public series pages, newsletter unsubscribe, admin analytics, review workflow, newsletter campaigns, series management, comment likes, and authenticated reading lists.
  • Frontend API clients and visible admin panels for analytics, reviews, newsletter campaigns, and series management.
  • Article page support for comment likes, API-backed related articles, author/profile navigation, and richer SEO metadata consumption.
  • Seed/reference content now finalizes 23 published articles, refreshes old dates/slugs, promotes former draft/review items, and adds Phase 9 SEO fields.
  • Sitemap/RSS generation now uses canonical paths, dateModified, metaDescription, and full-content RSS payloads.

Not run locally by request: frontend/backend builds and Prisma generation. Validate on Linux with the commands in ARCH.md/AGENTS.md.

All work targets the SERVER/ directory. Content research uses web search and Context7 for technical accuracy. Schema changes are additive (nullable/defaulted) and backward-compatible.


Overview

Phase 9 has three pillars:

  1. Content modernization. 17 of 18 articles have placeholder content. All are dated 1403. AI model references are two generations stale. The publication needs to be current, factually accurate, and complete.
  2. Publication features. Ten features a real tech publication needs but Teknav currently lacks: newsletter campaigns, article series, related articles, comment votes, writer analytics, syntax highlighting, social share, admin time-series charts, editorial review workflow, and a bookmark/reading-list system.
  3. SEO enrichment. Every article needs metaDescription, Open Graph metadata, JSON-LD structured data, and a canonical URL. OG images generated locally. Sitemap and RSS updated.

9.1 — Article Content Expansion

Goal

Full Persian prose for all 17 stub articles + refresh art1. Every article must be factually current as of Ordibehesht 1405 / April 2026.

Research Process (per article)

  1. Web search the topic for the latest benchmarks, releases, and numbers.
  2. Context7 for articles touching library APIs, language specs, or framework internals (Rust, Kubernetes, WebAssembly).
  3. Apply the model reference mapping below throughout.

Model Reference Update Table

Outdated reference Replace with (1405 / 2026)
Claude 3 Opus Claude Opus 4.7
GPT-4 / GPT-4 Turbo GPT-4o / o3
Gemini Ultra / Gemini 1.5 Gemini 2.5 Pro
Phi-3 Phi-4
Mistral 7B / Mistral Large Mistral Large 2
Llama 3.2 Llama 4 Scout / Maverick
H100 (as cutting-edge) GB200 / Blackwell
B200 (as upcoming) B200 (shipping), GB200 NVL72 (current flagship)
NeurIPS 2024 / ICML 2024 NeurIPS 2025 / ICML 2025

Content Requirements (per article)

  • Minimum 800 words of substantive Persian prose.
  • At least two <h2> and one <h3>.
  • One <blockquote> citing a real expert or published finding.
  • One <div class="insight-box"> with a key takeaway.
  • One <div class="footnote-box"> citing sources.
  • Factually current as of Ordibehesht 1405.

Article Date Refresh

ID Slug New Date (Persian) New dateEn
art1 ai-clean-data ۱۴۰۵/۰۲/۰۱ 2026-04-21
art2 custom-ai-chips ۱۴۰۵/۰۱/۲۵ 2026-04-14
art3 browser-architecture ۱۴۰۵/۰۱/۲۰ 2026-04-09
art4 software-supply-chain ۱۴۰۵/۰۱/۱۵ 2026-04-04
art5 iranian-startups-scale ۱۴۰۵/۰۱/۱۰ 2026-03-30
art6 small-llms ۱۴۰۵/۰۱/۰۵ 2026-03-25
art7 edge-computing ۱۴۰۴/۱۲/۲۵ 2026-03-16
art8 synthetic-data ۱۴۰۴/۱۲/۱۵ 2026-03-06
art9 ai-hardware-roadmap ۱۴۰۴/۱۲/۰۵ 2026-02-24
art10 quantum-computing ۱۴۰۴/۱۱/۲۵ 2026-02-14
art11 microservices-vs-monolith ۱۴۰۴/۱۱/۱۵ 2026-02-04
art12 fintech-regulation ۱۴۰۴/۱۱/۰۵ 2026-01-25
art13 llm-security ۱۴۰۴/۱۰/۲۵ 2026-01-15
art14 data-governance ۱۴۰۴/۱۰/۱۵ 2026-01-05
art15 robotics-warehouse ۱۴۰۴/۱۰/۰۵ 2025-12-26
art16 open-source-ai ۱۴۰۴/۰۹/۲۵ 2025-12-16
art17 rust-systems ۱۴۰۴/۰۹/۱۵ 2025-12-06
art18 venture-capital-ai ۱۴۰۴/۰۹/۰۵ 2025-11-26

Rename art10 slug: quantum-computing-2024 → quantum-computing.


9.2 — New Articles (1405 Topics)

Five new articles on topics that did not exist or were not prominent in 1403.

ID Slug Title Category Author Date
art19 claude-opus-47-reasoning کلود اوپوس ۴.۷ و تحول در مدل‌های استدلالی ai آرسام صباغ ۱۴۰۵/۰۲/۰۸
art20 agentic-ai-production هوش مصنوعی عاملی از آزمایشگاه تا تولید ai آرسام صباغ ۱۴۰۵/۰۲/۰۵
art21 vibe-coding وایب‌کدینگ و آینده برنامه‌نویسی با هوش مصنوعی software سیداحمدرضا محجوب ۱۴۰۵/۰۱/۲۸
art22 ai-energy-crisis بحران انرژی مراکز داده: قیمت واقعی هوش مصنوعی future رادمان قلیچی ۱۴۰۵/۰۱/۲۲
art23 llama4-open-frontier Llama 4 و مرز جدید مدل‌های متن‌باز ai آرسام صباغ ۱۴۰۵/۰۱/۱۸

art19: Claude Opus 4.7 معرفی، مقایسه با o3 و Gemini 2.5 Pro، بنچمارک‌های MATH/GPQA/SWE-bench، دلالت‌های عملی برای توسعه‌دهندگان.

art20: چرخه think-act-observe، معماری‌های multi-agent، ریسک‌های autonomy، مقایسه Claude Code، OpenAI Operator، Google Mariner.

art21: تعریف Vibe Coding (اصطلاح Karpathy)، تأثیر بر چرخه توسعه، نقد جدی‌پذیری، مقایسه Cursor، Copilot، Claude Code.

art22: مصرف برق مراکز داده AI (500+ TWh در 2026)، چالش‌های شبکه، PUE، تأثیر بر اهداف کربن.

art23: Llama 4 Scout/Maverick/Behemoth، معماری MoE، مقایسه با GPT-4o و Claude Sonnet 4.6، استقرار on-premise.


9.3 — SEO Metadata Layer

New Fields per Article in teknav-data.js

{
  metaDescription: '...',      // 120–155 chars, Persian plain text
  keywords: ['کلمه ۱', ...],   // 6–10 Persian terms
  ogTitle: '...',              // og:title override (defaults to title)
  ogDescription: '...',        // 200–300 chars for og:description
  ogImage: '/images/og/slug.jpg',
  canonicalPath: '/articles/slug',
  dateModified: '2026-...',    // ISO — for JSON-LD dateModified and sitemap <lastmod>
}

Prisma Schema Changes

model Article {
  // existing fields unchanged ...
  metaDescription  String?   @db.VarChar(160)
  keywords         String[]  @default([])
  ogTitle          String?   @db.VarChar(200)
  ogDescription    String?   @db.VarChar(300)
  ogImage          String?
  canonicalPath    String?
  dateModified     DateTime?
}

All nullable/defaulted — no migration failure on existing rows. After change: prisma:generate → prisma:apply.

seed.ts Update

Extend the article upsert to include all new SEO fields.

Article Detail Page <head> Block

<title>{title} | تکناو</title>
<meta name="description" content="{metaDescription}" />
<meta name="keywords" content="{keywords.join(',')}" />
<link rel="canonical" href="https://www.teknav.ir{canonicalPath}" />

<meta property="og:type" content="article" />
<meta property="og:title" content="{ogTitle || title}" />
<meta property="og:description" content="{ogDescription || metaDescription}" />
<meta property="og:image" content="https://www.teknav.ir{ogImage}" />
<meta property="og:url" content="https://www.teknav.ir{canonicalPath}" />
<meta property="og:locale" content="fa_IR" />
<meta property="og:site_name" content="تکناو" />
<meta property="article:published_time" content="{dateEn}" />
<meta property="article:modified_time" content="{dateModified}" />
<meta property="article:author" content="{authorName}" />
<meta property="article:section" content="{categoryName}" />

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="{ogTitle || title}" />
<meta name="twitter:description" content="{metaDescription}" />
<meta name="twitter:image" content="https://www.teknav.ir{ogImage}" />

JSON-LD Structured Data

Inject <script type="application/ld+json"> in article detail page:

{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "{title}",
  "description": "{metaDescription}",
  "datePublished": "{dateEn}",
  "dateModified": "{dateModified}",
  "inLanguage": "fa",
  "author": {
    "@type": "Person",
    "name": "{authorName}",
    "url": "https://www.teknav.ir/profile/@{authorUsername}"
  },
  "publisher": {
    "@type": "Organization",
    "name": "تکناو",
    "url": "https://www.teknav.ir",
    "logo": { "@type": "ImageObject", "url": "https://www.teknav.ir/favicon.png" }
  },
  "image": "https://www.teknav.ir{ogImage}",
  "mainEntityOfPage": { "@type": "WebPage", "@id": "https://www.teknav.ir{canonicalPath}" }
}

9.4 — OG Image Generation

Node script scripts/generate-og-images.mjs uses @napi-rs/canvas (npm, zero CDN) to produce 23 × 1200×630 JPEG files in public/images/og/. Each image: category brand color background, site name top-right, article title centered (Vazirmatn font loaded locally), category badge bottom-right.


9.5 — SEO Artifacts Refresh

  • Update scripts/generate-seo.mjs to use dateModified in sitemap <lastmod> and metaDescription in RSS <description>.
  • RSS <content:encoded> must include the full article HTML.
  • Run npm run seo:sitemap after all content changes.

9.6 — Article Status Cleanup

art17 (rust-systems) is پیش‌نویس and art18 (venture-capital-ai) is در انتظار بررسی. Both get full content in Phase 9 and are promoted to منتشرشده.


9.7 — Newsletter Campaign System

Goal

The platform already collects subscriber emails. Phase 9 closes the loop: admins compose and send a newsletter campaign to all confirmed subscribers directly from the admin panel.

Schema

model NewsletterCampaign {
  id          String    @id @default(cuid())
  subject     String    @db.VarChar(200)
  bodyHtml    String    @db.Text
  sentAt      DateTime?
  recipientCount Int    @default(0)
  createdBy   User      @relation(fields: [createdById], references: [id])
  createdById Int
  createdAt   DateTime  @default(now())
}

Backend Routes

Method Path Auth Description
GET /api/v1/admin/newsletter/campaigns admin List all campaigns
POST /api/v1/admin/newsletter/campaigns admin Create draft campaign
POST /api/v1/admin/newsletter/campaigns/:id/send admin Send to all active subscribers
GET /api/v1/admin/newsletter/subscribers admin List subscribers

Send Logic

  • POST .../send is idempotent — once sentAt is set, re-sending returns 409.
  • Uses existing backend/src/lib/email.ts SMTP sender.
  • Sends in batches of 50 with a 200ms delay between batches to avoid SMTP rate limits.
  • Each email includes a plain-text unsubscribe link: https://www.teknav.ir/api/v1/newsletter/unsubscribe?token={token}.
  • Each NewsletterSubscriber row gets a unique unsubscribeToken (random UUID, generated on subscription).
  • GET /api/v1/newsletter/unsubscribe?token= removes the subscriber and returns a plain 200.

Schema Addition to NewsletterSubscriber

model NewsletterSubscriber {
  // existing fields ...
  unsubscribeToken String @unique @default(cuid())
}

Frontend (Admin)

  • New "خبرنامه" tab in admin sidebar (admin role only).
  • Campaign list with status (پیش‌نویس / ارسال‌شده), subject, recipient count, send date.
  • Campaign composer: subject input + HTML textarea (same editor controls as article editor).
  • Preview renders the HTML in a sandboxed <iframe>.
  • "ارسال به همه مشترکان" button with a count confirmation dialog: "ارسال به ۱٬۲۳۴ مشترک؟".
  • Subscriber list panel: table of emails + subscribe date + unsubscribe button.

9.8 — Article Series

Goal

Group thematically linked articles into ordered series (e.g., «سری: راهنمای کامل LLMها»). Readers see their position in the series and can navigate between parts. Great for long-form coverage and reader retention.

Schema

model ArticleSeries {
  id          String          @id @default(cuid())
  title       String          @db.VarChar(200)
  slug        String          @unique
  description String?         @db.VarChar(500)
  coverImage  String?
  createdAt   DateTime        @default(now())
  articles    SeriesArticle[]
}

model SeriesArticle {
  id        String        @id @default(cuid())
  series    ArticleSeries @relation(fields: [seriesId], references: [id], onDelete: Cascade)
  seriesId  String
  article   Article       @relation(fields: [articleId], references: [id], onDelete: Cascade)
  articleId String
  position  Int

  @@unique([seriesId, articleId])
  @@unique([seriesId, position])
}

Backend Routes

Method Path Auth Description
GET /api/v1/series public List all series
GET /api/v1/series/:slug public Series detail with ordered articles
GET /api/v1/admin/series editor+ Admin list
POST /api/v1/admin/series editor+ Create series
PATCH /api/v1/admin/series/:id editor+ Update title/description/cover and ordered article list
DELETE /api/v1/admin/series/:id editor+ Delete series

Frontend

  • Series page /series/:slug — cover, description, ordered article list with part numbers (قسمت ۱، قسمت ۲...).
  • Article detail — if the article belongs to a series, show a "این مقاله بخشی از سری..." banner above the content. Banner includes prev/next navigation.
  • Admin series management — new "سری‌ها" tab in admin sidebar. Create/edit series, drag-and-drop to reorder articles using a simple up/down button pair (no drag library needed).

Goal

Show three related articles at the bottom of every article. Relation is computed by tag overlap — no ML needed. Results cached in Redis.

Backend

New route: GET /api/v1/articles/:slug/related — returns up to 3 articles.

Algorithm:

  1. Get the current article's tag IDs.
  2. Find published articles sharing the most tags (excluding self), limited to 3.
  3. Tiebreak by views DESC.
  4. Cache result in Redis related:{articleId} with TTL 1 hour.
  5. Clear cache when any article is published, edited, or deleted.
-- Prisma raw for tag overlap score
SELECT a.id, COUNT(at.tagId) AS overlap
FROM Article a
JOIN ArticleTag at ON at.articleId = a.id
WHERE at.tagId IN (<current article tag IDs>)
  AND a.id != <current article id>
  AND a.status = 'منتشرشده'
GROUP BY a.id
ORDER BY overlap DESC, a.views DESC
LIMIT 3

Frontend

  • RelatedArticles component at the bottom of ArticleDetail, after comments.
  • Three cards in a horizontal row (single column on mobile), each showing cover color, title, category, read time, author.
  • Loaded via GET /api/v1/articles/:slug/related on article mount.

9.10 — Comment Helpfulness Votes

Goal

Readers can upvote useful comments. Comments with more votes sort higher. Discourages noise and surfaces quality discussion.

Schema

model CommentLike {
  id        String   @id @default(cuid())
  comment   Comment  @relation(fields: [commentId], references: [id], onDelete: Cascade)
  commentId String
  user      User     @relation(fields: [userId], references: [id])
  userId    Int
  createdAt DateTime @default(now())

  @@unique([commentId, userId])
}

Add likeCount Int @default(0) to the Comment model.

Backend Routes

Method Path Auth Description
POST /api/v1/comments/:id/like session Toggle like (like if not liked, unlike if already liked)

GET /api/v1/articles/:slug/comments — add likeCount and likedByMe (bool, based on session) to each comment object. Default sort changes to: top-level comments ordered by likeCount DESC, createdAt DESC.

Frontend

  • Each comment card shows a "👍 مفید بود ({likeCount})" button (no emoji in production — use an SVG thumbs-up from global SVG sprite or inline).
  • Authenticated users can click to toggle. The count updates optimistically.
  • The button is teal-accented when liked by the current user, muted otherwise.
  • Guests see the count but cannot vote.

9.11 — Writer Analytics Dashboard

Goal

Writers see per-article performance stats: views over time, reaction breakdown, comment counts, and follower growth. Editors and admins see the same for any article. Data visualised with pure SVG sparklines — no chart library dependency.

Backend Routes

Method Path Auth Description
GET /api/v1/auth/analytics/overview session (writer+) Total views, reactions, comments, followers
GET /api/v1/auth/analytics/articles session (writer+) Per-article stats list
GET /api/v1/auth/analytics/article/:slug session (writer+, must own or be editor+) Daily view/reaction time-series for one article (last 30 days)

Time-Series Data Source

The existing ActivityLog table logs article publishes. Views are buffered in Redis and flushed to the Article.views field. For a time-series breakdown, add:

model ArticleViewLog {
  id        String   @id @default(cuid())
  article   Article  @relation(fields: [articleId], references: [id], onDelete: Cascade)
  articleId String
  date      DateTime @db.Date
  views     Int      @default(0)

  @@unique([articleId, date])
}

flushViewCounters() (which already runs every 60s) is extended to also upsert a ArticleViewLog row for today's date. This gives per-day granularity.

Frontend

  • New "آمار" tab in the writer's profile settings panel (visible to own writer/editor/admin).
  • Overview cards: کل بازدیدها، کل واکنش‌ها، کل نظرها، دنبال‌کنندگان.
  • Article table: slug, title, views, reactions, comments — sortable.
  • Clicking an article row shows a 30-day sparkline (SVG, pure path element) for views.
  • SVG sparkline: 300×60px, line chart, no axes — just the trend shape.

9.12 — Local Syntax Highlighting

Goal

Technical articles with <pre><code> blocks render highlighted code. Highlight.js installed via npm (not CDN). Auto-detects language from class="language-*". RTL article layout must not break LTR code blocks.

Implementation

Install: npm install highlight.js in root package.json.

In teknav-articles.jsx, after the article HTML is injected via dangerouslySetInnerHTML, use a useEffect to:

  1. Query all pre code elements inside the article body.
  2. Call hljs.highlightElement(el) on each.
  3. Force direction: ltr; text-align: left on every <pre> via inline style so RTL layout doesn't reverse code.

Import only the languages needed to keep bundle size small:

  • javascript, typescript, python, bash, sql, json, rust, go, yaml, docker

Register them with hljs.registerLanguage(...). Do not use hljs.highlightAll() (too broad).

Add a highlight.js theme (github-dark.min.css) to src/styles/ — copied from the npm package's styles/ folder, never from a CDN.

Add a language label above each code block: read the class attribute, strip language-, show as a small badge (e.g., python).

Zero CDN Check

scripts/check-no-cdn.mjs must not flag highlight.js. Since it's bundled via Vite from npm, it produces a local /assets/... chunk — no external URL. Verify after npm run build.


9.13 — Social Share (URL-Scheme Only)

Goal

Readers can share articles to Telegram, Twitter/X, and WhatsApp, and copy the article URL to clipboard. Zero CDN — all share actions use URL schemes or the browser Clipboard API. No tracking pixels.

Frontend

SocialShare component rendered in ArticleDetail below the article byline.

Share targets:

Platform URL scheme
Telegram https://t.me/share/url?url={url}&text={title}
Twitter/X https://x.com/intent/tweet?url={url}&text={title}
WhatsApp https://api.whatsapp.com/send?text={title}+{url}
Copy link navigator.clipboard.writeText(url) → toast "لینک کپی شد"

All share URLs open in a new tab (target="_blank" rel="noopener"). Icons are inline SVGs from a local sprite (no CDN icon fonts). Buttons are small and unobtrusive — placed in a compact row right of the article metadata line.

Share count is NOT tracked (avoids the need for a counter service or extra storage).


9.14 — Admin Time-Series Analytics

Goal

Admin and editor roles see trend charts in the dashboard: daily active users (7-day), new registrations (30-day), total article views (30-day), and top 5 articles of the week. All charts are pure SVG — no chart.js or D3 dependency.

Backend Routes

Method Path Auth Description
GET /api/v1/admin/analytics writer+ Views by day and top articles
GET /api/v1/admin/analytics/content writer+ Content performance analytics
GET /api/v1/admin/analytics/newsletter editor+ Newsletter analytics
GET /api/v1/admin/analytics/engagement writer+ Engagement event analytics

Data Source

User registrations: aggregate User.createdAt by date.

Views: sum ArticleViewLog.views grouped by date (requires 9.11's ArticleViewLog model).

Top articles: sum ArticleViewLog.views where date >= 7 days ago, group by article, top 5.

Frontend

  • New "آمار سایت" panel in admin dashboard (below the live feed widget).
  • Two SVG bar charts: "ثبت‌نام روزانه (۳۰ روز)" and "بازدید روزانه (۳۰ روز)".
  • Bar chart: 300×120px SVG. Bars drawn as <rect> elements, scaled to max value. Day labels below each bar (Persian numerals, rotated 45°). Hover tooltip showing exact value.
  • Top articles table: rank, title (linked), category, 7-day views.

SVG Bar Chart Component

Pure React component, no dependencies:

function BarChart({ data, label }) {
  const max = Math.max(...data.map(d => d.value), 1);
  const W = 300, H = 100, BAR_W = Math.floor(W / data.length) - 2;
  return (
    <svg width={W} height={H} style={{ direction: 'ltr' }}>
      {data.map((d, i) => {
        const h = Math.round((d.value / max) * (H - 20));
        return <rect key={i} x={i * (BAR_W + 2)} y={H - 20 - h} width={BAR_W} height={h} fill="#0F6B73" rx={2} />;
      })}
    </svg>
  );
}

9.15 — Editorial Submission & Review Workflow

Goal

Writers submit articles for editorial review. Editors see a "صف بررسی" (review queue) panel. Editors can approve (publish), request revisions, or reject with a note. Writers get an in-app notification of the decision.

Schema

enum ReviewStatus {
  pending
  revision_requested
  approved
  rejected
}

model ArticleReview {
  id        String       @id @default(cuid())
  article   Article      @relation(fields: [articleId], references: [id], onDelete: Cascade)
  articleId String       @unique
  reviewer  User?        @relation(fields: [reviewerId], references: [id])
  reviewerId Int?
  status    ReviewStatus @default(pending)
  note      String?      @db.VarChar(1000)
  createdAt DateTime     @default(now())
  updatedAt DateTime     @updatedAt
}

The existing Article.status enum gains a new value: در_انتظار_بررسی (already exists in data but now is the formal submission state).

Backend Routes

Method Path Auth Description
GET /api/v1/admin/reviews writer+ List review queue items visible to the current role
PUT /api/v1/admin/articles/:id/review reviewer/editor/admin Approve, request revision, or reject with note

Notification Events

Reuse the Phase 8 Notification model with new types:

enum NotificationType {
  comment
  comment_reply
  new_article
  review_approved    // new
  review_revision    // new
  review_rejected    // new
  review_submitted   // new — sent to editors when writer submits
}

Frontend

  • Writer article editor — "ارسال برای بررسی" button visible when article is in draft or revision_requested state.
  • Admin sidebar — new "صف بررسی" tab with a badge showing pending count (editor+ only).
  • Review queue panel — article title, author, category, word count, submit date. Action buttons: تأیید و انتشار / درخواست اصلاح / رد کردن. "درخواست اصلاح" and "رد کردن" open a note textarea before sending.
  • Writer notification — bell notification with the editor's decision and note.
  • Article status badge — ArticleDetail in admin view shows the current review status clearly.

9.16 — Saved Articles & Reading List Enhancement

Goal

Phase 4 added a save API but the frontend reading list is minimal. Phase 9 elevates it: users can organize saved articles into named lists (e.g., "بعداً بخوانم"، "مقالات مرجع")، reorder items, and access the list from their profile.

Schema

model ReadingList {
  id        String            @id @default(cuid())
  user      User              @relation(fields: [userId], references: [id], onDelete: Cascade)
  userId    Int
  name      String            @db.VarChar(80)
  isDefault Boolean           @default(false)
  createdAt DateTime          @default(now())
  items     ReadingListItem[]

  @@unique([userId, name])
}

model ReadingListItem {
  id            String      @id @default(cuid())
  list          ReadingList @relation(fields: [listId], references: [id], onDelete: Cascade)
  listId        String
  article       Article     @relation(fields: [articleId], references: [id], onDelete: Cascade)
  articleId     String
  position      Int
  addedAt       DateTime    @default(now())

  @@unique([listId, articleId])
}

Each user gets a default list "ذخیره‌شده" created on first save. The existing SavedArticle model (Phase 4) is migrated to this system.

Backend Routes

Method Path Auth Description
GET /api/v1/auth/lists session Get user's reading lists
POST /api/v1/auth/lists session Create a list
DELETE /api/v1/auth/lists/:id session Delete a list (not default)
POST /api/v1/auth/lists/:id/items session Add article to list
DELETE /api/v1/auth/lists/:id/items/:articleId session Remove article
PUT /api/v1/auth/lists/:id/reorder session Reorder items

Frontend

  • Profile "ذخیره‌شده" tab shows reading lists, each expandable to see its articles.
  • "+" button to create a new named list.
  • Save button on articles now opens a small dropdown: pick which list to save to (or create new).
  • Reading list items show article cover color, title, category, and a remove button.

Dependencies to Add

Package Location Purpose
@napi-rs/canvas root (devDependency) OG image generation script
highlight.js root Local syntax highlighting

Both installed via npm. Neither violates the zero-CDN policy — they are bundled locally by Vite.


Implementation Sequence

  1. Web-search each article topic for 1405/2026 state.
  2. Write full content for art2–art18 + update art1.
  3. Write full content for art19–art23 (new articles).
  4. Add SEO fields to all 23 articles in teknav-data.js, update dates.
  5. Rename art10 slug.
  6. Add SEO + ArticleViewLog + NewsletterCampaign + ArticleSeries + SeriesArticle + CommentLike + ArticleReview + ReviewStatus + ReadingList + ReadingListItem + extended NotificationType to schema.prisma.
  7. Run prisma:generate → prisma:apply.
  8. Update seed.ts to upsert SEO fields and create default reading lists for seeded users.
  9. Implement backend routes: newsletter campaigns (9.7), series (9.8), related articles (9.9), comment likes (9.10), writer analytics + ArticleViewLog flush (9.11), admin analytics (9.14), editorial review (9.15), reading lists (9.16).
  10. Update article detail page: full <head> meta + JSON-LD + SocialShare + RelatedArticles (9.3, 9.13, 9.9).
  11. Implement syntax highlighting in article body (9.12).
  12. Admin: newsletter campaign panel (9.7), series management (9.8), review queue (9.15), subscriber list (9.7), analytics charts (9.14).
  13. Writer profile: analytics tab (9.11), reading lists tab (9.16).
  14. Write scripts/generate-og-images.mjs, install @napi-rs/canvas, run script.
  15. Update scripts/generate-seo.mjs for dateModified and RSS <content:encoded>.
  16. Run npm run seo:sitemap.
  17. Run npm run build — verify zero CDN, check highlight.js bundle chunk is local.
  18. Run cd backend && npm run build.

Phase 9 Checklist

Current implementation status: core schema, seed/reference data, SEO artifacts, public APIs, admin APIs, and primary frontend entry points are implemented. Several originally listed polish items remain intentionally pending for Linux/browser validation or a later UI pass: full researched 800-word rewrites for every legacy article, syntax-highlighting runtime integration, dedicated writer analytics profile tab, richer social-share buttons, article-series prev/next navigation, review notifications, and reading-list picker UI.

Content

  • Web-search each article topic for 1405/2026 facts
  • Write full content for art2 (custom-ai-chips)
  • Write full content for art3 (browser-architecture)
  • Write full content for art4 (software-supply-chain)
  • Write full content for art5 (iranian-startups-scale)
  • Write full content for art6 (small-llms) — Phi-3→Phi-4, Llama 3.2→Llama 4
  • Write full content for art7 (edge-computing)
  • Write full content for art8 (synthetic-data)
  • Write full content for art9 (ai-hardware-roadmap) — H100→GB200
  • Write full content for art10 (quantum-computing) — rename slug
  • Write full content for art11 (microservices-vs-monolith)
  • Write full content for art12 (fintech-regulation)
  • Write full content for art13 (llm-security) — Claude 3→Claude Opus 4.7
  • Write full content for art14 (data-governance)
  • Write full content for art15 (robotics-warehouse)
  • Write full content for art16 (open-source-ai) — Llama 3.2→Llama 4
  • Write full content for art17 (rust-systems) + promote to منتشرشده
  • Write full content for art18 (venture-capital-ai) + promote to منتشرشده
  • Update art1 (ai-clean-data) — refresh model refs and 2024 citations
  • Write full content for art19 (claude-opus-47-reasoning)
  • Write full content for art20 (agentic-ai-production)
  • Write full content for art21 (vibe-coding)
  • Write full content for art22 (ai-energy-crisis)
  • Write full content for art23 (llama4-open-frontier)

SEO Metadata

  • Add SEO fields to all 23 articles in teknav-data.js
  • Update all date/dateEn fields per date table
  • Rename quantum-computing-2024 slug → quantum-computing
  • Article detail page: inject full <head> meta block
  • Article detail page: inject JSON-LD structured data
  • Verify meta tags in browser DevTools

OG Images

  • Write scripts/generate-og-images.mjs
  • npm install --save-dev @napi-rs/canvas
  • Run script → verify 23 JPEGs in public/images/og/

SEO Artifacts

  • Update scripts/generate-seo.mjs (dateModified, metaDescription, content:encoded)
  • Run npm run seo:sitemap
  • Verify sitemap <lastmod> for all 23 slugs
  • Verify RSS <content:encoded> is populated

Schema & Seed

  • Add SEO columns to Article in schema.prisma
  • Add ArticleViewLog model
  • Add NewsletterCampaign model + unsubscribeToken to NewsletterSubscriber
  • Add ArticleSeries + SeriesArticle models
  • Add CommentLike model + likeCount to Comment
  • Add ArticleReview model + ReviewStatus enum
  • Extend NotificationType enum with review notification types
  • Add ReadingList + ReadingListItem models
  • Run prisma:generate + prisma:apply on Linux host
  • Update seed.ts to upsert SEO fields
  • Run npm run seed on Linux host

Newsletter Campaigns (9.7)

  • Backend: campaign CRUD + send route with batch SMTP
  • Backend: GET /api/v1/newsletter/unsubscribe?token= route
  • Admin: "خبرنامه" sidebar tab
  • Admin: campaign list + composer + HTML preview iframe
  • Admin: subscriber list with remove action

Article Series (9.8)

  • Backend: public series routes (GET /api/v1/series, GET /api/v1/series/:slug)
  • Backend: admin series CRUD + article add/remove/reorder
  • Frontend: /series/:slug series landing page
  • Frontend: series banner + prev/next nav in ArticleDetail
  • Admin: "سری‌ها" sidebar tab with series management
  • Backend: GET /api/v1/articles/:slug/related with tag-overlap algorithm + Redis cache
  • Frontend: RelatedArticles component in ArticleDetail

Comment Likes (9.10)

  • Backend: POST /api/v1/comments/:id/like (toggle)
  • Backend: add likeCount + likedByMe to comment listing
  • Frontend: like button with count on each comment card
  • Frontend: optimistic update on click

Writer Analytics (9.11)

  • Backend: extend flushViewCounters() to upsert ArticleViewLog
  • Backend: GET /api/v1/auth/analytics/overview
  • Backend: GET /api/v1/auth/analytics/articles
  • Backend: GET /api/v1/auth/analytics/article/:slug
  • Frontend: "آمار" tab in writer profile settings
  • Frontend: SVG sparkline component for 30-day view trend

Syntax Highlighting (9.12)

  • npm install highlight.js
  • Copy github-dark.min.css theme to src/styles/
  • Import and register 10 languages in teknav-articles.jsx
  • useEffect to apply hljs.highlightElement() on article body <code> blocks
  • Force direction: ltr on all <pre> blocks
  • Add language label badge above each code block
  • npm run build — confirm no CDN URLs in dist/

Social Share (9.13)

  • SocialShare component with Telegram, Twitter/X, WhatsApp, copy-link
  • Inline SVG icons for each platform
  • Toast "لینک کپی شد" on clipboard copy
  • Integrate in ArticleDetail below byline

Admin Time-Series Analytics (9.14)

  • Backend: GET /api/v1/admin/analytics
  • Backend: GET /api/v1/admin/analytics/content
  • Backend: GET /api/v1/admin/analytics/newsletter
  • Backend: GET /api/v1/admin/analytics/engagement
  • Frontend: BarChart SVG component
  • Admin dashboard: "آمار سایت" panel with two bar charts + top-articles table

Editorial Review Workflow (9.15)

  • Backend: GET /api/v1/admin/reviews
  • Backend: PUT /api/v1/admin/articles/:id/review
  • Backend: approve / revise / reject routes
  • Backend: notification on each decision + on submission
  • Frontend: "ارسال برای بررسی" button in article editor
  • Admin: "صف بررسی" sidebar tab with pending badge
  • Admin: review queue panel with decision buttons + note textarea

Reading Lists (9.16)

  • Backend: reading list CRUD + item add/remove/reorder
  • Frontend: profile "ذخیره‌شده" tab shows named lists
  • Frontend: article save button opens list-picker dropdown
  • Frontend: list item card with remove button
  • Migrate existing SavedArticle rows to default reading list in seed.ts

Build & Docs

  • npm run build passes (zero CDN check)
  • cd backend && npm run build passes
  • Update MEMORY.md
  • Update ARCH.md
  • Update PHASES.md
  • Mark PHASE9.md complete