Files
teknav-bak/app/PHASE11.md
T
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

364 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 11 - Production Launch + Platform Completeness
## Status: Implemented (Linux production validation pending)
## Goal
Ship `https://www.teknav.ir` by completing Linux production validation (Track 0), then close the 13 confirmed platform gaps across admin completeness, reader UX, monetization activation, and reliability without introducing new dependencies or architectural shifts.
---
## Track 0 — Production Gate `[BLOCKING]`
All items in this track must be completed before any Phase 11 feature work begins.
### 0.1 Linux Validation Sequence
Run in order on the production or staging Linux host:
| Step | Command |
|------|---------|
| Generate Prisma client | `cd backend && npm run prisma:generate` |
| Apply schema | `cd backend && npm run prisma:apply` |
| Seed database | `cd backend && npm run seed` |
| Install frontend deps (if needed) | `npm install` |
| Generate SEO artifacts | `npm run seo:sitemap` |
| Generate OG images | `npm run og:images` |
| Build frontend | `npm run build` |
| Build backend | `cd backend && npm run build` |
| Run smoke tests | `npm run smoke:prod` |
### 0.2 Functional Verification Checklist
After schema apply, manually verify each flow:
- [ ] Login by email and by `@username`
- [ ] Signup with display name, username, password, email/phone
- [ ] SMS 2FA enable from profile → login returns `tek_2fa` ticket → `/api/v1/auth/2fa/verify` completes session
- [ ] Google/GitHub OAuth callback and session creation
- [ ] Password reset via SMTP
- [ ] Email verification via SMTP
- [ ] Profile edit for reader, writer, editor, reviewer, admin roles
- [ ] Password change with current-password confirmation
- [ ] Avatar upload and WebP conversion
- [ ] Article create → review workflow → publish (writer → reviewer → editor/admin)
- [ ] Scheduled publish fires via `teknav-publish-due.timer`
- [ ] Comments, replies, likes, flags, upvotes
- [ ] Reading history tracked on article view
- [ ] Reading lists — save to default list, list management
- [ ] Notifications and SSE stream at `/api/v1/auth/notifications/stream`
- [ ] Admin SSE at `/api/v1/admin/stream` (visitor counter, activity)
- [ ] Writer/topic follows and follow counts
- [ ] Tag pages at `/tag/:tag`
- [ ] Topic hub pages at `/topics/ai` etc.
- [ ] Newsletter archive at `/newsletter` and `/newsletter/:slug`
- [ ] Series pages at `/series/:slug`
- [ ] Glossary pages at `/glossary/:slug`
- [ ] Reactions (heart/fire/thinking) on articles
- [ ] Reading streak increments on daily visits
- [ ] Continue-reading progress saved in `ReadHistory.progress`
- [ ] Q&A endpoints at `/api/v1/articles/:slug/qa`
- [ ] `/jobs`, `/courses`, `/membership` pages render
- [ ] Premium article content locking for non-premium readers
- [ ] Sponsored label visible on promoted articles
- [ ] Errata notices on corrected articles
- [ ] Admin analytics at `/api/v1/admin/analytics/*`
- [ ] Panel metrics at `/api/v1/admin/panel-metrics`
- [ ] SEO audit at `/api/v1/admin/seo/audit`
- [ ] First-party error capture at `/api/v1/errors`
- [ ] `/ACCOUNTS.md` returns 403/404 through nginx
- [ ] `/sitemap.xml`, `/robots.txt`, `/feed.xml` return 2xx
### 0.3 Credential Rotation and Hardening
- [ ] Rotate all seed/admin passwords after first production login
- [ ] Remove `resetPasswordOnSeed` flag from writer-admin credentials in `backend/prisma/credentials.json`
- [ ] Confirm SMTP credentials in `/etc/teknav/backend.env`
- [ ] Confirm Kavenegar API key in `/etc/teknav/backend.env`
- [ ] Confirm `OAUTH_CALLBACK_BASE=https://www.teknav.ir` in env
- [ ] Verify `ACCOUNTS.md` is gitignored and HTTP-blocked
---
## Track 1 — Admin + Editor Completeness `[Quick wins — backend fully built]`
All 5 items in this track have complete backend support. Only frontend wiring is missing.
### 1.1 Sponsored / premiumOnly / featured Toggles in ArticleEditor `[S]` ✓ DONE
**Gap:** `Article.sponsored`, `Article.premiumOnly`, and `Article.featured` exist in the Prisma schema and are returned by the admin articles API, but the `ArticleEditor` form in `teknav-admin.jsx` has no UI controls for them. A field helper (`fieldRow`) exists and is used for other boolean fields; these three are simply not called.
**Fix:** Add three toggle rows in the ArticleEditor meta-panel under a new "نشانه‌گذاری" (Flags) collapsible group:
- `premiumOnly` — "فقط اعضای ویژه" with lock icon
- `sponsored` — "محتوای حمایت‌شده" with badge icon
- `featured` — "مقاله ویژه" with star icon
**Files:** `teknav-admin.jsx` → `ArticleEditor` meta-panel
---
### 1.2 Content Freshness Filter in ArticlesManagement `[S]` ✓ DONE
**Gap:** `Article.contentFreshnessStatus` (values: `up_to_date`, `needs_update`, `outdated`) was added in Phase 10 but the `ArticlesManagement` filter bar only filters by `status` (draft/review/published etc.), not by freshness.
**Fix:** Add a `<select>` freshness filter dropdown alongside the existing status filter. Values: همه | به‌روز | نیاز به بازنگری | قدیمی. Wire to the existing articles list query param.
**Files:** `teknav-admin.jsx` → `ArticlesManagement`
---
### 1.3 SEO Audit Dedicated Tab `[S]` ✓ DONE
**Gap:** The SEO audit data from `GET /api/v1/admin/seo/audit` is fetched and displayed inline inside the analytics panel with a hard `.slice(0,8)` cap, and there is no dedicated tab for it. Issues beyond 8 are silently hidden.
**Fix:** Add a `seo-audit` tab to the `AdminPanel` tab bar. Move the full audit table there: show all issues paginated, add severity badges (خطا / هشدار / پیشنهاد), an article link per row, and a summary count header ("X مشکل یافت شد").
**Files:** `teknav-admin.jsx` → `AdminPanel` tabs, remove inline audit from analytics panel
---
### 1.4 Engagement Analytics Dedicated Tab `[S]` ✓ DONE
**Gap:** `GET /api/v1/admin/analytics/engagement` returns event-type counts, but the admin analytics panel only shows them as static count pills with no time-series, no per-article breakdown, and no comparison window.
**Fix:** Add an `engagement` sub-tab inside the existing analytics panel. Show:
- 7-day event count bar chart (reuse `WeeklyChart` or the filled-area SVG sparkline pattern)
- Event-type breakdown table: type | count | percentage of total
- Top 5 articles by engagement event count
**Files:** `teknav-admin.jsx` → analytics panel, `src/lib/admin-api.js` if endpoint not already called
---
### 1.5 Bulk Comment Moderation UI `[M]` ✓ DONE
**Gap:** `POST /api/v1/admin/comments/bulk` with `{ ids: [], action: 'delete'|'approve'|'flag' }` exists in the backend but the `CommentsModeration` panel has no checkboxes or bulk-action toolbar.
**Fix:**
- Add a checkbox column to each comment row (left side, RTL-aware)
- Show a floating bulk-action bar when ≥1 comment is checked: "X مورد انتخاب شده | حذف | تأیید | علامت‌گذاری | لغو انتخاب"
- Wire to `bulkModerateComments(ids, action)` in `src/lib/admin-api.js` (method already exists)
- Clear selection and refresh list after action completes
**Files:** `teknav-admin.jsx` → `CommentsModeration`, `src/lib/admin-api.js`
---
## Track 2 — Reader Experience `[API infrastructure ready]`
### 2.1 Article Card Save List Picker `[S]` ✓ DONE
**Gap:** Saving an article from the article card (home feed, search results, topic pages) calls `contentApi.saveArticle()` directly, which saves to the default list "بعداً بخوانم" without giving the reader a choice. The `ReadingListModal` (list picker) exists in `teknav-articles.jsx` for the full article detail page but is not wired to cards.
**Fix:** Lift `ReadingListModal` (or extract it to a shared location) so it can be triggered from `ArticleCard`. On card save-icon click: open the list picker modal, show user's reading lists + "ایجاد لیست جدید", save to the selected list.
**Files:** `teknav-home.jsx` (or wherever `ArticleCard` lives), `teknav-articles.jsx` (source of `ReadingListModal`)
---
### 2.2 Scheduled-Publish Topic Notifications Bug Fix `[S]` ✓ DONE
**Gap:** `backend/scripts/publish-due.mjs` sends writer-follow notifications when a scheduled article is published, but it does NOT look up `TopicFollow` records and notify topic followers. The `TopicFollow` model and the notification-creation pattern are already in the codebase from Phase 10.
**Fix:** After publishing a scheduled article in `publish-due.mjs`, query `TopicFollow` for the article's topic slug, then insert `Notification` records for each topic follower (same pattern as writer-follow notifications already in the script).
**Files:** `backend/scripts/publish-due.mjs`
---
### 2.3 Continue-Reading Progress Overlay on Article Cards `[S]` ✓ DONE
**Gap:** `ReadHistory.progress` (0–100 integer) is stored per article per user, and the backend returns it in history API responses. Article cards do not show any reading progress indicator, so readers cannot see which articles they have partially read.
**Fix:** On authenticated article card render, check if the article slug appears in the user's recent read history (available from `engagementApi.getHistory()`). If `progress` is between 5 and 95, show a thin progress bar at the bottom of the card image and a "ادامه مطالعه" chip.
**Files:** `teknav-home.jsx` → `ArticleCard`, `src/lib/engagement-api.js`
---
### 2.4 Search Console / Bing Webmaster Verification `[S, operational]` [DONE]
**Gap:** Site verification files for Google Search Console and Bing Webmaster Tools are not in `public/`. These are static files the crawlers expect (e.g. `google<token>.html` and `BingSiteAuth.xml`).
**Fix:** Add placeholder files to `public/` with documented variable names. Operator fills in the actual tokens after production DNS is live. Add them to `sitemap.xml` exclusion so they don't show up as content pages.
**Files:** `public/google<token>.html` (placeholder), `public/BingSiteAuth.xml` (placeholder), `scripts/generate-seo.mjs`
---
## Track 3 — Monetization & Premium
### 3.1 Premium Paywall Gate Card UX Upgrade `[S]` ✓ DONE
**Gap:** Premium article content locking in `teknav-articles.jsx` shows a single bare CTA button with no value proposition, no feature list, and no visual differentiation from the article content above. This produces low conversion.
**Fix:** Replace the bare CTA with a styled "قفل ویژه" gate card:
- Blurred/faded article excerpt above the gate (CSS `mask-image` gradient)
- Crown/lock icon + "این مقاله برای اعضای ویژه" headline
- 3-bullet benefit list (دسترسی به همه مقالات ویژه / بدون تبلیغ / حمایت از تکناو)
- Primary CTA: "عضویت ویژه" → `/membership`
- Secondary: "ورود به حساب" if user is not authenticated
**Files:** `teknav-articles.jsx` → premium gate section
---
### 3.2 Zarinpal Membership Subscription Flow `[L]` ✓ DONE
**Gap:** The `/membership` page is a static positioning page with no payment wiring. `User.membershipTier` exists in the schema but there is no route that upgrades it.
**Design decisions:**
- Payment gateway: **Zarinpal** for IRR transactions; Stripe noted as fallback for USD
- Flow: `/membership` → POST `/api/v1/membership/subscribe` → redirect to Zarinpal → callback to `/api/v1/membership/callback` → set `User.membershipTier = 'premium'` → redirect to `/membership/success`
**New backend routes in `backend/src/routes/monetization.ts`:**
- `POST /api/v1/membership/subscribe` — create Zarinpal payment request, return redirect URL
- `GET /api/v1/membership/callback` — verify Zarinpal callback, upgrade tier, create `Notification`
- `GET /api/v1/membership/status` — return current tier for authenticated user
**Frontend changes:**
- `/membership` page: wire CTA to `POST /api/v1/membership/subscribe`
- `/membership/success` route: show confirmation and "بازگشت به خانه"
- Profile settings: show current membership tier badge
**Files:** `backend/src/routes/monetization.ts`, `teknav-articles.jsx` (membership page), `backend/prisma/schema.prisma` (add `MembershipPayment` model for audit trail)
---
### 3.3 Job Listing Admin Panel `[M]` ✓ DONE
**Gap:** `JobListing` has a `status` field (`pending`, `approved`, `rejected`) and the public `/jobs` endpoint filters for `approved` only. Company submissions go through `POST /api/v1/jobs` but there is no admin UI or admin API endpoint to review and approve/reject pending listings.
**New backend endpoint:**
- `GET /api/v1/admin/jobs` — list all job listings with status filter
- `PATCH /api/v1/admin/jobs/:id` — set status to `approved` or `rejected`, optionally set `featuredUntil`
**Frontend:** Add a "آگهی‌های شغلی" tab to AdminPanel. Show a table: company | title | location | status | submitted date | actions (تأیید / رد / ویژه). Wire to new admin API methods in `src/lib/admin-api.js`.
**Files:** `backend/src/routes/monetization.ts`, `teknav-admin.jsx` → AdminPanel tabs, `src/lib/admin-api.js`
---
### 3.4 Premium Enforcement Audit `[S]` ✓ DONE (confirmed, no changes needed)
**Gap:** `Article.premiumOnly` is stored and returned by the admin API, but a review of `backend/src/routes/public.ts` is needed to confirm that `publicArticle()` strips article body content for non-premium authenticated users and unauthenticated users.
**Fix:** Audit `publicArticle()` in `public.ts`. If body is not stripped, add a content-gate: if `article.premiumOnly && (!user || user.membershipTier !== 'premium')`, return `body: null` and `premiumGated: true` in the serialized response. Confirm the frontend gate card is triggered by `premiumGated: true`.
**Files:** `backend/src/routes/public.ts`, `teknav-articles.jsx`
---
## Track 4 — Reliability & Scale
### 4.1 Automated Test Suite `[L]`
**Gap:** `backend/tests/phase10-smoke.test.mjs` has 3 tests (auth, article fetch, comment post). There are no tests for: auth edge cases, profile editing, reading lists, comment moderation, analytics endpoints, panel metrics, or the review workflow.
**Plan:**
- Expand `backend/tests/` with test files per domain: `auth.test.mjs`, `articles.test.mjs`, `engagement.test.mjs`, `admin.test.mjs`
- Each file: happy path + one error case per endpoint
- Use Node `test` runner (already used in smoke file) — no new test framework
- Target: ~40 tests total; run via `cd backend && npm test`
- Tests run against a local test database (separate `TEST_DATABASE_URL` env var)
**Files:** `backend/tests/`, `backend/package.json` (test script already wired)
---
### 4.2 First-Party Error Aggregation Dashboard `[M]` ✓ DONE
**Gap:** `POST /api/v1/errors` (in `backend/src/routes/errors.ts`) logs client errors to Pino but does not persist them. There is no way for ops/admin to see frontend error counts, top error messages, or affected users.
**Design decision:** Enhance first-party `/api/v1/errors` — consistent with zero-CDN, privacy-first architecture. No self-hosted GlitchTip or external SaaS.
**Backend changes:**
- Add `ClientError` model to `backend/prisma/schema.prisma`: `id`, `message`, `stack`, `url`, `userId` (nullable), `userAgent`, `createdAt`
- `POST /api/v1/errors` persists to `ClientError` in addition to Pino log
- `GET /api/v1/admin/errors` — aggregated view: top 10 error messages by count, count by day (last 30 days), most recent 20 raw errors (admin only)
**Frontend changes:**
- Add "خطاهای کلاینت" tab to AdminPanel (admin-only visibility)
- Show: daily error count sparkline, top-errors table (message | count | last seen), recent raw errors list
**Files:** `backend/prisma/schema.prisma`, `backend/src/routes/errors.ts`, `teknav-admin.jsx`, `src/lib/admin-api.js`
---
### 4.3 S3 Object Storage Interface Design `[M]` ✓ DONE (LocalStorageAdapter active, S3 stub ready)
**Gap:** Uploads go to `/var/lib/teknav/uploads` on local disk. When upload volume outgrows disk, migration to S3-compatible storage (Liara Object Storage, ArvanCloud, or MinIO) will be needed. No interface layer exists; storage path is hardcoded.
**Design (no production wiring in Phase 11):**
- Extract a `StorageAdapter` interface in `backend/src/lib/storage.ts`: `upload(file, key) → url`, `delete(key)`, `url(key)`
- Implement `LocalStorageAdapter` (current behavior, no change)
- Implement `S3StorageAdapter` stub (wired but inactive; activated by `STORAGE_BACKEND=s3` env var)
- Switch upload route in `backend/src/routes/admin.ts` to use the adapter
- Document required env vars: `STORAGE_BACKEND`, `S3_ENDPOINT`, `S3_BUCKET`, `S3_ACCESS_KEY`, `S3_SECRET_KEY`
**Files:** `backend/src/lib/storage.ts` (new), `backend/src/routes/admin.ts` (upload handler)
---
### 4.4 Meilisearch Evaluation `[S]`
**Gap:** Persian FTS is handled by PostgreSQL `to_tsvector` with a custom Persian configuration. As article volume grows, ranking quality and response time may degrade. Meilisearch supports Persian (no stemming required for most Persian queries) and has a clean REST API.
**Plan:**
- Stand up a Meilisearch instance on the staging server alongside PostgreSQL
- Index the current 23 published articles
- Run identical queries through both PostgreSQL FTS and Meilisearch
- Measure: result relevance (manual spot-check), P95 query latency, index rebuild time
- Write a short evaluation note (2–3 paragraphs) as a follow-up to this phase
- Production wiring deferred to Phase 12 only if evaluation warrants
**Files:** `backend/src/routes/public.ts` (search endpoint, read-only audit), evaluation note in `docs/meilisearch-eval.md`
---
## Complexity Reference
| ID | Task | Complexity | Track |
|----|------|-----------|-------|
| 1.1 | Sponsored/premiumOnly/featured toggles | S | Admin |
| 1.2 | Content freshness filter | S | Admin |
| 1.3 | SEO audit dedicated tab | S | Admin |
| 1.4 | Engagement analytics dedicated tab | S | Admin |
| 1.5 | Bulk comment moderation UI | M | Admin |
| 2.1 | Article card save list picker | S | Reader |
| 2.2 | Scheduled-publish topic notifications fix | S | Reader |
| 2.3 | Continue-reading overlay on cards | S | Reader |
| 2.4 | Search Console/Bing verification files | S | Ops |
| 3.1 | Premium gate card UX upgrade | S | Monetization |
| 3.2 | Zarinpal membership subscription flow | L | Monetization |
| 3.3 | Job listing admin panel | M | Monetization |
| 3.4 | Premium enforcement audit | S | Monetization |
| 4.1 | Automated test suite | L | Reliability |
| 4.2 | First-party error aggregation dashboard | M | Reliability |
| 4.3 | S3 storage interface design | M | Reliability |
| 4.4 | Meilisearch evaluation | S | Reliability |
S = hours | M = 1–2 days | L = 3–5 days
---
## Implementation Order
All batches 1–4 are complete. Remaining:
### Batch 5 — Deferred (Phase 12 candidates)
- `4.1` Automated test suite (~40 tests) — `backend/tests/`
- `4.4` Meilisearch evaluation on staging → `docs/meilisearch-eval.md`
- S3 storage wiring (when upload volume requires it)
---
## Constraints (carry-forward from Phase 10)
- **Zero CDN.** No third-party hosted JS, CSS, fonts, or assets. Zarinpal redirect is a server-side redirect — no inline scripts.
- **Persian/RTL first.** All new UI labels in Persian; logical CSS properties; no left/right assumptions.
- **No mock admin data.** Every new admin panel or tab must use real API data and show a proper empty state.
- **No Docker.** Production remains native Linux/systemd. S3 storage interface may reference MinIO as a local-dev option only.
- **`ACCOUNTS.md` stays protected.** No new paths or rewrites that could expose it.
- **Backend APIs for all persistence.** Frontend never touches the DB or session store directly.