1003 lines
40 KiB
HTML
1003 lines
40 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ar" dir="rtl">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>الدليل الشامل لمشروع SaaS Meta Ads</title>
|
|
<style>
|
|
:root {
|
|
--bg: #0b1020;
|
|
--panel: #121933;
|
|
--panel-2: #0f1530;
|
|
--text: #eef2ff;
|
|
--muted: #b8c0e0;
|
|
--line: #243057;
|
|
--accent: #7aa2ff;
|
|
--accent-2: #8ef0d0;
|
|
--code-bg: #0a0f1f;
|
|
--shadow: 0 16px 40px rgba(0,0,0,.28);
|
|
--radius: 20px;
|
|
}
|
|
|
|
* { box-sizing: border-box; }
|
|
html { scroll-behavior: smooth; }
|
|
body {
|
|
margin: 0;
|
|
font-family: "Tahoma", "Segoe UI", Arial, sans-serif;
|
|
background:
|
|
radial-gradient(circle at top right, rgba(122,162,255,.18), transparent 28%),
|
|
radial-gradient(circle at top left, rgba(142,240,208,.08), transparent 24%),
|
|
linear-gradient(180deg, #08101d 0%, #0b1020 100%);
|
|
color: var(--text);
|
|
line-height: 1.9;
|
|
}
|
|
|
|
.container {
|
|
width: min(1180px, calc(100% - 32px));
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.hero {
|
|
padding: 54px 0 28px;
|
|
}
|
|
|
|
.hero-card {
|
|
background: linear-gradient(180deg, rgba(18,25,51,.92), rgba(9,14,29,.96));
|
|
border: 1px solid rgba(255,255,255,.08);
|
|
border-radius: 28px;
|
|
box-shadow: var(--shadow);
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
.hero-card::before {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 0;
|
|
background: linear-gradient(135deg, rgba(122,162,255,.18), transparent 45%, rgba(142,240,208,.08));
|
|
pointer-events: none;
|
|
}
|
|
|
|
.hero-inner {
|
|
position: relative;
|
|
padding: 44px 40px 34px;
|
|
}
|
|
|
|
.eyebrow {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
color: var(--accent-2);
|
|
font-size: 14px;
|
|
background: rgba(142,240,208,.08);
|
|
border: 1px solid rgba(142,240,208,.18);
|
|
border-radius: 999px;
|
|
padding: 8px 14px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
h1 {
|
|
margin: 0 0 12px;
|
|
font-size: clamp(30px, 4vw, 52px);
|
|
line-height: 1.25;
|
|
}
|
|
|
|
.subtitle {
|
|
color: var(--muted);
|
|
font-size: 18px;
|
|
margin: 0;
|
|
max-width: 900px;
|
|
}
|
|
|
|
.meta-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
|
|
gap: 14px;
|
|
margin-top: 28px;
|
|
}
|
|
|
|
.meta-box {
|
|
background: rgba(255,255,255,.03);
|
|
border: 1px solid rgba(255,255,255,.07);
|
|
border-radius: 18px;
|
|
padding: 16px 18px;
|
|
}
|
|
|
|
.meta-box strong {
|
|
display: block;
|
|
font-size: 13px;
|
|
color: var(--accent-2);
|
|
margin-bottom: 6px;
|
|
letter-spacing: .2px;
|
|
}
|
|
|
|
.layout {
|
|
display: grid;
|
|
grid-template-columns: 310px minmax(0, 1fr);
|
|
gap: 24px;
|
|
align-items: start;
|
|
padding-bottom: 48px;
|
|
}
|
|
|
|
.toc {
|
|
position: sticky;
|
|
top: 18px;
|
|
background: rgba(14,20,40,.92);
|
|
border: 1px solid rgba(255,255,255,.08);
|
|
border-radius: 24px;
|
|
padding: 22px 20px;
|
|
box-shadow: var(--shadow);
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.toc h2 {
|
|
margin: 0 0 14px;
|
|
font-size: 20px;
|
|
}
|
|
|
|
.toc a {
|
|
display: block;
|
|
color: var(--muted);
|
|
text-decoration: none;
|
|
padding: 8px 10px;
|
|
border-radius: 12px;
|
|
transition: .18s ease;
|
|
font-size: 15px;
|
|
}
|
|
|
|
.toc a:hover {
|
|
background: rgba(122,162,255,.12);
|
|
color: var(--text);
|
|
}
|
|
|
|
main section {
|
|
background: rgba(16,22,46,.9);
|
|
border: 1px solid rgba(255,255,255,.08);
|
|
border-radius: 24px;
|
|
padding: 30px;
|
|
box-shadow: var(--shadow);
|
|
margin-bottom: 22px;
|
|
}
|
|
|
|
h2.section-title {
|
|
margin: 0 0 18px;
|
|
font-size: 30px;
|
|
line-height: 1.35;
|
|
}
|
|
|
|
h3 {
|
|
margin: 22px 0 10px;
|
|
font-size: 22px;
|
|
color: #f8fbff;
|
|
}
|
|
|
|
h4 {
|
|
margin: 18px 0 8px;
|
|
font-size: 18px;
|
|
color: var(--accent-2);
|
|
}
|
|
|
|
p, li {
|
|
color: var(--text);
|
|
font-size: 17px;
|
|
}
|
|
|
|
.muted {
|
|
color: var(--muted);
|
|
}
|
|
|
|
ul, ol {
|
|
margin: 10px 0 0;
|
|
padding: 0 22px 0 0;
|
|
}
|
|
|
|
li + li { margin-top: 8px; }
|
|
|
|
.callout {
|
|
border-right: 4px solid var(--accent);
|
|
background: linear-gradient(180deg, rgba(122,162,255,.08), rgba(122,162,255,.03));
|
|
padding: 16px 18px;
|
|
border-radius: 16px;
|
|
margin: 14px 0;
|
|
color: var(--text);
|
|
}
|
|
|
|
.grid-2 {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
gap: 16px;
|
|
}
|
|
|
|
.card {
|
|
background: rgba(255,255,255,.03);
|
|
border: 1px solid rgba(255,255,255,.07);
|
|
border-radius: 18px;
|
|
padding: 18px;
|
|
}
|
|
|
|
.pill {
|
|
display: inline-block;
|
|
background: rgba(122,162,255,.14);
|
|
color: #d7e5ff;
|
|
border: 1px solid rgba(122,162,255,.22);
|
|
border-radius: 999px;
|
|
padding: 4px 10px;
|
|
font-size: 13px;
|
|
margin-inline-end: 8px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.code-block {
|
|
margin: 16px 0 22px;
|
|
background: var(--code-bg);
|
|
border: 1px solid rgba(122,162,255,.18);
|
|
border-radius: 18px;
|
|
overflow: hidden;
|
|
box-shadow: inset 0 1px 0 rgba(255,255,255,.04);
|
|
}
|
|
|
|
.code-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid rgba(255,255,255,.08);
|
|
background: linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,255,255,.01));
|
|
font-size: 14px;
|
|
color: var(--muted);
|
|
}
|
|
|
|
.dots {
|
|
display: inline-flex;
|
|
gap: 6px;
|
|
}
|
|
|
|
.dot {
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
background: rgba(255,255,255,.18);
|
|
}
|
|
|
|
pre {
|
|
margin: 0;
|
|
padding: 18px;
|
|
overflow: auto;
|
|
direction: ltr;
|
|
text-align: left;
|
|
color: #eaf1ff;
|
|
font-size: 14px;
|
|
line-height: 1.7;
|
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
tab-size: 2;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
}
|
|
|
|
code { font-family: inherit; }
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-top: 14px;
|
|
border-radius: 16px;
|
|
overflow: hidden;
|
|
border: 1px solid rgba(255,255,255,.08);
|
|
}
|
|
|
|
th, td {
|
|
padding: 14px 12px;
|
|
text-align: right;
|
|
border-bottom: 1px solid rgba(255,255,255,.06);
|
|
vertical-align: top;
|
|
}
|
|
|
|
th {
|
|
background: rgba(122,162,255,.12);
|
|
color: #f5f8ff;
|
|
font-size: 15px;
|
|
}
|
|
|
|
td {
|
|
color: var(--muted);
|
|
font-size: 15px;
|
|
}
|
|
|
|
.journey {
|
|
position: relative;
|
|
padding-right: 26px;
|
|
margin-top: 14px;
|
|
}
|
|
|
|
.journey::before {
|
|
content: "";
|
|
position: absolute;
|
|
right: 6px;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 2px;
|
|
background: linear-gradient(180deg, var(--accent), transparent);
|
|
}
|
|
|
|
.journey-item {
|
|
position: relative;
|
|
padding: 0 0 18px;
|
|
}
|
|
|
|
.journey-item::before {
|
|
content: "";
|
|
position: absolute;
|
|
right: -24px;
|
|
top: 10px;
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
background: var(--accent-2);
|
|
box-shadow: 0 0 0 5px rgba(142,240,208,.12);
|
|
}
|
|
|
|
footer {
|
|
padding: 12px 0 48px;
|
|
color: var(--muted);
|
|
text-align: center;
|
|
}
|
|
|
|
@media (max-width: 980px) {
|
|
.layout { grid-template-columns: 1fr; }
|
|
.toc { position: relative; top: 0; }
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.hero-inner { padding: 28px 20px 22px; }
|
|
main section { padding: 22px 18px; }
|
|
h2.section-title { font-size: 25px; }
|
|
p, li { font-size: 16px; }
|
|
}
|
|
|
|
@media print {
|
|
body { background: #fff; color: #111; }
|
|
.toc { position: relative; }
|
|
.hero-card, .toc, main section {
|
|
box-shadow: none;
|
|
background: #fff;
|
|
color: #111;
|
|
border: 1px solid #ddd;
|
|
}
|
|
.subtitle, .muted, td, .toc a { color: #333; }
|
|
.code-block { border: 1px solid #bbb; }
|
|
pre { color: #111; background: #fafafa; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header class="hero">
|
|
<div class="container">
|
|
<div class="hero-card">
|
|
<div class="hero-inner">
|
|
<div class="eyebrow">📘 دليل تعليمي عملي • NestJS Backend • SaaS Meta Ads</div>
|
|
<h1>الدليل الشامل والكامل لمشروع <span style="color: var(--accent-2);">SaaS Meta Ads</span></h1>
|
|
<p class="subtitle">
|
|
وثيقة تعليمية مرتبة بصيغة موقع HTML، موجهة لفهم بنية مشروع Back-end حقيقي مبني باستخدام NestJS، مع شرح تدريجي للمفاهيم، تدفق البيانات، الموديولات الأساسية، والأمثلة البرمجية بشكل واضح وسهل القراءة.
|
|
</p>
|
|
<div class="meta-grid">
|
|
<div class="meta-box"><strong>نوع المشروع</strong>منصة SaaS لتحليل وإدارة إعلانات Meta</div>
|
|
<div class="meta-box"><strong>الإطار المستخدم</strong>NestJS + TypeScript + Node.js</div>
|
|
<div class="meta-box"><strong>الهدف التعليمي</strong>فهم معماريّة Backend حقيقية خطوة بخطوة</div>
|
|
<div class="meta-box"><strong>أسلوب العرض</strong>RTL، منظم، وأقرب لكتاب تقني تعليمي</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="container layout">
|
|
<aside class="toc">
|
|
<h2>فهرس المحتوى</h2>
|
|
<a href="#intro">1) ما هو المشروع؟</a>
|
|
<a href="#stack">2) الأساس التقني</a>
|
|
<a href="#root">3) جولة في النواة</a>
|
|
<a href="#metaads">4) موديول Meta Ads</a>
|
|
<a href="#analytics">5) التحليلات والذكاء الاصطناعي</a>
|
|
<a href="#automation">6) محرك الأتمتة</a>
|
|
<a href="#notifications">7) التنبيهات</a>
|
|
<a href="#payments">8) الدفع والفلوس</a>
|
|
<a href="#auth">9) الأمان والاشتراكات</a>
|
|
<a href="#errors">10) التعامل مع الأخطاء</a>
|
|
<a href="#entities">11) الجداول والبيانات</a>
|
|
<a href="#glossary">12) قاموس المصطلحات</a>
|
|
<a href="#journey">13) رحلة البيانات</a>
|
|
<a href="#extras">14) تفاصيل إضافية مهمة</a>
|
|
<a href="#closing">15) خلاصة عملية</a>
|
|
</aside>
|
|
|
|
<main>
|
|
<section id="intro">
|
|
<h2 class="section-title">1) ما هو هذا المشروع أصلاً؟</h2>
|
|
<p>
|
|
تخيّل أن لديك عدة حملات إعلانية تعمل على Meta، وربما لاحقاً على TikTok أو Google. بدلاً من الدخول إلى كل منصة بشكل منفصل لمتابعة الأداء، يقوم هذا المشروع ببناء <strong>مركز قيادة موحد</strong> يجلب الأرقام، يحللها، يكتشف الخلل، ويرسل تنبيهات أو ينفذ إجراءات تلقائياً عند الحاجة.
|
|
</p>
|
|
<div class="callout">
|
|
الفكرة الجوهرية هنا ليست فقط <strong>عرض الأرقام</strong>، بل بناء نظام يقرأ الأداء ويحوّله إلى قرارات عملية.
|
|
</div>
|
|
<div class="grid-2">
|
|
<div class="card">
|
|
<h4>ما الذي يفعله النظام؟</h4>
|
|
<ul>
|
|
<li>يربط الحساب الإعلاني بالمستخدم.</li>
|
|
<li>يجلب الإحصاءات من Meta Graph API.</li>
|
|
<li>يوحد البيانات ويحللها.</li>
|
|
<li>يستخدم AI لإعطاء توصيات مفهومة.</li>
|
|
<li>ينفذ قواعد أتمتة مثل إيقاف الحملات الخاسرة.</li>
|
|
<li>يرسل تنبيهات إلى الهاتف أو تيليجرام أو Slack.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="card">
|
|
<h4>لماذا هذا النوع من المشاريع مهم؟</h4>
|
|
<ul>
|
|
<li>يمثل مشروع SaaS حقيقي يمكن بيعه باشتراك شهري.</li>
|
|
<li>يجمع بين Backend Architecture و Integrations و AI.</li>
|
|
<li>يعلمك كيف تُبنى الأنظمة الكبيرة بشكل منظم.</li>
|
|
<li>يفتح الباب لإضافة TikTok أو Google Ads لاحقاً بسهولة.</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="stack">
|
|
<h2 class="section-title">2) الأساس الذي بنينا عليه المشروع (Tech Stack)</h2>
|
|
<p>اعتمد المشروع على مجموعة تقنيات تكمل بعضها البعض. الفهم هنا مهم، لأن كل تقنية لها دور محدد داخل النظام.</p>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>التقنية</th>
|
|
<th>الدور داخل المشروع</th>
|
|
<th>لماذا اختيرت؟</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>Node.js</td>
|
|
<td>محرك تشغيل JavaScript على السيرفر</td>
|
|
<td>سريع ومناسب لـ I/O وطلبات APIs</td>
|
|
</tr>
|
|
<tr>
|
|
<td>TypeScript</td>
|
|
<td>إضافة أنواع واضحة للكود</td>
|
|
<td>تقليل الأخطاء وتحسين القراءة والتنظيم</td>
|
|
</tr>
|
|
<tr>
|
|
<td>NestJS</td>
|
|
<td>Framework منظم لبناء Back-end</td>
|
|
<td>معمارية قوية تشبه الأنظمة الكبيرة</td>
|
|
</tr>
|
|
<tr>
|
|
<td>TypeORM / PostgreSQL</td>
|
|
<td>التعامل مع البيانات والجداول</td>
|
|
<td>هيكلة واضحة وقاعدة بيانات قوية</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Axios / HttpService</td>
|
|
<td>التواصل مع خدمات خارجية</td>
|
|
<td>إرسال طلبات HTTP إلى Meta وTelegram وغيرها</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h3>المفاهيم الأساسية في NestJS</h3>
|
|
<div>
|
|
<span class="pill">Module</span>
|
|
<span class="pill">Controller</span>
|
|
<span class="pill">Service</span>
|
|
<span class="pill">Entity</span>
|
|
<span class="pill">Guard</span>
|
|
<span class="pill">Filter</span>
|
|
</div>
|
|
<ul>
|
|
<li><strong>Module:</strong> صندوق يجمع الوظائف المتقاربة في مكان واحد.</li>
|
|
<li><strong>Controller:</strong> يستقبل الطلب القادم من الواجهة أو التطبيق.</li>
|
|
<li><strong>Service:</strong> ينفذ المنطق الفعلي في الخلفية.</li>
|
|
<li><strong>Entity:</strong> يصف شكل الجدول داخل قاعدة البيانات.</li>
|
|
<li><strong>Guard:</strong> يفحص هل للمستخدم الحق في الوصول أم لا.</li>
|
|
<li><strong>Filter:</strong> يتعامل مع الأخطاء ويحوّلها إلى رد نظيف ومنظم.</li>
|
|
</ul>
|
|
</section>
|
|
|
|
<section id="root">
|
|
<h2 class="section-title">3) جولة في النواة (The Root)</h2>
|
|
<h3><code>src/main.ts</code> — الشرارة الأولى</h3>
|
|
<p>
|
|
هذا هو أول ملف يُنفذ عند تشغيل المشروع. دوره هو إنشاء التطبيق، تجهيز الإعدادات العامة، وإخبار السيرفر أن يبدأ الاستماع للطلبات.
|
|
</p>
|
|
<div class="code-block">
|
|
<div class="code-head"><span>main.ts</span><span class="dots"><span class="dot"></span><span class="dot"></span><span class="dot"></span></span></div>
|
|
<pre><code>async function bootstrap() {
|
|
// إنشاء التطبيق بناءً على الموديول الرئيسي
|
|
const app = await NestFactory.create(AppModule);
|
|
|
|
// كل الروابط ستبدأ بـ /api
|
|
app.setGlobalPrefix('api');
|
|
|
|
// إعداد Swagger لتوثيق الـ API
|
|
const config = new DocumentBuilder()
|
|
.setTitle('SaaS Meta')
|
|
.build();
|
|
|
|
// تشغيل السيرفر على المنفذ 3001
|
|
await app.listen(3001);
|
|
}</code></pre>
|
|
</div>
|
|
<div class="callout">
|
|
عملياً، إذا لم يعمل <code>main.ts</code> فلن يعمل المشروع كله، لأنه نقطة الإقلاع الأساسية.
|
|
</div>
|
|
|
|
<h3><code>src/app.module.ts</code> — الدماغ المركزي</h3>
|
|
<p>
|
|
هذا الملف يربط كل موديولات المشروع ببعضها. أي جزء غير مسجل هنا غالباً لن يكون معروفاً للتطبيق.
|
|
</p>
|
|
<div class="code-block">
|
|
<div class="code-head"><span>app.module.ts</span><span class="dots"><span class="dot"></span><span class="dot"></span><span class="dot"></span></span></div>
|
|
<pre><code>@Module({
|
|
imports: [
|
|
ConfigModule.forRoot({ isGlobal: true }),
|
|
TypeOrmModule.forRoot({ /* database config */ }),
|
|
MetaAdsModule,
|
|
AutomationModule,
|
|
AuthModule,
|
|
UsersModule,
|
|
PaymentsModule,
|
|
],
|
|
})
|
|
export class AppModule {}</code></pre>
|
|
</div>
|
|
|
|
<h3><code>configuration.ts</code> — الخزنة</h3>
|
|
<p>
|
|
هذا الملف يقرأ القيم السرية من <code>.env</code>، مثل مفاتيح Meta أو بيانات قاعدة البيانات. الهدف هو فصل الأسرار عن الكود.
|
|
</p>
|
|
<div class="code-block">
|
|
<div class="code-head"><span>.env مثال</span><span class="dots"><span class="dot"></span><span class="dot"></span><span class="dot"></span></span></div>
|
|
<pre><code>DB_HOST=localhost
|
|
DB_PORT=5432
|
|
DB_USER=postgres
|
|
DB_PASSWORD=secret
|
|
META_TOKEN=your_meta_token
|
|
GEMINI_API_KEY=your_gemini_key</code></pre>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="metaads">
|
|
<h2 class="section-title">4) موديول Meta Ads</h2>
|
|
<p>
|
|
هنا يبدأ المشروع بالتعامل المباشر مع Meta. هذه الطبقة مسؤولة عن الاتصال بـ Graph API، جلب البيانات، وتنظيفها لتصبح قابلة للفهم داخل النظام.
|
|
</p>
|
|
|
|
<h3><code>MetaAdsService</code> — المفاوض</h3>
|
|
<p>هذا الجزء يرسل الطلب إلى Meta ويسترجع الإحصاءات الأساسية للحملات.</p>
|
|
<div class="code-block">
|
|
<div class="code-head"><span>fetchInsights()</span><span class="dots"><span class="dot"></span><span class="dot"></span><span class="dot"></span></span></div>
|
|
<pre><code>async fetchInsights(adAccountId: string) {
|
|
const url = `https://graph.facebook.com/v19.0/${adAccountId}/insights`;
|
|
|
|
const response = await this.httpService.get(url, {
|
|
params: {
|
|
access_token: this.token,
|
|
fields: 'campaign_name,impressions,clicks,spend,ctr'
|
|
}
|
|
});
|
|
|
|
return response.data;
|
|
}</code></pre>
|
|
</div>
|
|
<ul>
|
|
<li>يبني رابط API الخاص بالحساب الإعلاني.</li>
|
|
<li>يرسل <code>access_token</code> حتى تسمح Meta بإرجاع البيانات.</li>
|
|
<li>يحدد الحقول المطلوبة فقط لتقليل الحمل غير الضروري.</li>
|
|
</ul>
|
|
|
|
<h3><code>MetaNormalizer</code> — عامل النظافة</h3>
|
|
<p>
|
|
بيانات Meta غالباً تعود بصيغة خام وغير موحدة. لذلك نحتاج طبقة تنظفها وتحولها إلى شكل موحد يعتمد عليه باقي المشروع.
|
|
</p>
|
|
<div class="code-block">
|
|
<div class="code-head"><span>normalize()</span><span class="dots"><span class="dot"></span><span class="dot"></span><span class="dot"></span></span></div>
|
|
<pre><code>static normalize(raw: any) {
|
|
return {
|
|
campaignId: raw.campaign_id,
|
|
campaignName: raw.campaign_name || 'اسم غير معروف',
|
|
impressions: parseInt(raw.impressions || '0'),
|
|
spend: parseFloat(raw.spend || '0'),
|
|
source: 'meta'
|
|
};
|
|
}</code></pre>
|
|
</div>
|
|
<div class="callout">
|
|
هذه الطبقة مهمة جداً. بدونها سيبقى كل مصدر بيانات يعيد لنا بنية مختلفة، وهذا يجعل النظام هشاً وصعب التوسعة لاحقاً.
|
|
</div>
|
|
</section>
|
|
|
|
<section id="analytics">
|
|
<h2 class="section-title">5) موديول التحليلات والذكاء الاصطناعي</h2>
|
|
<p>
|
|
بعد جلب البيانات، لا يكفي عرض الأرقام كما هي. هنا يبدأ دور التحليل: هل الحملة جيدة أم سيئة؟ هل الصورة ضعيفة؟ هل تكلفة النقرة مرتفعة؟ هل هناك شيء يستدعي تنبيهاً فورياً؟
|
|
</p>
|
|
|
|
<h3><code>AnalyticsService</code> — المحلل</h3>
|
|
<div class="code-block">
|
|
<div class="code-head"><span>analyzeCampaign()</span><span class="dots"><span class="dot"></span><span class="dot"></span><span class="dot"></span></span></div>
|
|
<pre><code>analyzeCampaign(data: any) {
|
|
const insights = [];
|
|
|
|
if (data.ctr < 1.0) {
|
|
insights.push({
|
|
type: 'warning',
|
|
code: 'LOW_CTR',
|
|
message: 'نسبة النقر ضعيفة! الجمهور يرى الإعلان ولكن لا ينقر عليه.',
|
|
recommendation: 'جرب تغيير صورة الإعلان أو العنوان لجذب الانتباه أكثر.'
|
|
});
|
|
}
|
|
|
|
if (data.cpc > 2.0) {
|
|
insights.push({
|
|
type: 'danger',
|
|
message: 'تكلفة النقرة غالية جداً!',
|
|
recommendation: 'راجع استهداف الجمهور، ربما تستهدف جمهوراً منافساً بشدة.'
|
|
});
|
|
}
|
|
|
|
return insights;
|
|
}</code></pre>
|
|
</div>
|
|
<p>
|
|
هذه التحليلات هي منطق برمجي صريح. أي أنها لا تعتمد على AI فقط، بل على قواعد واضحة يمكن الوثوق بها وتعديلها بسهولة.
|
|
</p>
|
|
|
|
<h3><code>AiService</code> — الخبير الذكي</h3>
|
|
<p>
|
|
هنا يدخل الذكاء الاصطناعي ليحوّل الأرقام إلى شرح بشري قابل للفهم. يمكنه مثلاً أن يقرأ الأداء ويكتب توصية بلغة واضحة، أو حتى يحلل صورة الإعلان إذا كان لدينا Vision model.
|
|
</p>
|
|
<div class="grid-2">
|
|
<div class="card">
|
|
<h4>استخدامات AI داخل المشروع</h4>
|
|
<ul>
|
|
<li>شرح أداء الحملة بلغة مفهومة.</li>
|
|
<li>اقتراح تحسينات على الجمهور أو الكرياتيف.</li>
|
|
<li>تحليل صورة الإعلان من ناحية الجاذبية والوضوح.</li>
|
|
<li>اكتشاف الأنماط التي قد لا يلتقطها الشرط البرمجي البسيط.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="card">
|
|
<h4>كيف تتكامل الخدمة مع النظام؟</h4>
|
|
<ul>
|
|
<li>تستقبل البيانات المنظفة من النظام.</li>
|
|
<li>يتم بناء Prompt واضح ومنضبط.</li>
|
|
<li>يعيد النموذج توصية أو ملخصاً أو تفسيراً.</li>
|
|
<li>يُعرض الناتج للمستخدم أو يستخدم ضمن التنبيهات.</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="automation">
|
|
<h2 class="section-title">6) محرك الأتمتة (Automation Engine)</h2>
|
|
<p>
|
|
هذا الجزء هو الذي يجعل المشروع أكثر من مجرد لوحة تحكم. هنا ننتقل من “المراقبة” إلى “التنفيذ الذكي”. المستخدم يضع قاعدة، والنظام ينفذها أوتوماتيكياً دون أن ينتظر تدخله اليدوي.
|
|
</p>
|
|
|
|
<h3><code>RuleEngineService</code> — المراقب</h3>
|
|
<div class="code-block">
|
|
<div class="code-head"><span>evaluateRule()</span><span class="dots"><span class="dot"></span><span class="dot"></span><span class="dot"></span></span></div>
|
|
<pre><code>async evaluateRule(rule: Rule) {
|
|
// 1) جلب البيانات الحالية من Meta
|
|
const currentData = await this.metaService.fetchInsights(rule.targetId);
|
|
|
|
// 2) فحص الشرط
|
|
const shouldTrigger = this.checkConditions(rule, currentData);
|
|
|
|
if (shouldTrigger) {
|
|
// 3) تنفيذ الإجراء المطلوب
|
|
if (rule.action === 'PAUSE') {
|
|
await this.metaService.updateStatus(rule.targetId, 'PAUSED');
|
|
this.logger.log(`تم إيقاف الحملة ${rule.targetId} بطلب من الأتمتة.`);
|
|
}
|
|
}
|
|
}</code></pre>
|
|
</div>
|
|
|
|
<h3>الفكرة العملية للقواعد</h3>
|
|
<ul>
|
|
<li>إذا تجاوز الصرف 100 دولار بدون مبيعات → أوقف الحملة.</li>
|
|
<li>إذا انخفض CTR عن حد معين → أرسل تحذيراً.</li>
|
|
<li>إذا ارتفع CPA كثيراً → أبلغ المستخدم فوراً.</li>
|
|
</ul>
|
|
|
|
<h3>Cron Jobs — التوقيت</h3>
|
|
<p>
|
|
لكي تعمل الأتمتة تلقائياً، لا بد من وجود آلية زمنية تكرر الفحص كل فترة. هنا يأتي دور المهام المجدولة.
|
|
</p>
|
|
<div class="code-block">
|
|
<div class="code-head"><span>مثال Cron</span><span class="dots"><span class="dot"></span><span class="dot"></span><span class="dot"></span></span></div>
|
|
<pre><code>@Cron('0 * * * *')
|
|
handleHourlyChecks() {
|
|
// كل ساعة يتم فحص القواعد المسجلة
|
|
}</code></pre>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="notifications">
|
|
<h2 class="section-title">7) التنبيهات</h2>
|
|
<p>
|
|
عندما يكتشف النظام مشكلة أو ينفذ إجراءً مهماً، يجب أن يبلغ المستخدم فوراً. التنبيه ليس مجرد ميزة إضافية؛ بل جزء أساسي من قيمة المنتج.
|
|
</p>
|
|
<h3><code>NotificationService</code> — ساعي البريد</h3>
|
|
<div class="code-block">
|
|
<div class="code-head"><span>send()</span><span class="dots"><span class="dot"></span><span class="dot"></span><span class="dot"></span></span></div>
|
|
<pre><code>async send(message: string, channels: string[]) {
|
|
for (const channel of channels) {
|
|
if (channel === 'telegram') {
|
|
await this.http.post('https://api.telegram.org/...', { text: message });
|
|
}
|
|
|
|
if (channel === 'fcm') {
|
|
await this.firebase.send({ body: message });
|
|
}
|
|
}
|
|
}</code></pre>
|
|
</div>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>القناة</th>
|
|
<th>الاستخدام</th>
|
|
<th>الفائدة</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>Telegram</td>
|
|
<td>رسائل مباشرة عبر Bot</td>
|
|
<td>سريع وسهل للمطور والمستخدم</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Slack</td>
|
|
<td>إشعار لفريق العمل</td>
|
|
<td>مفيد للشركات والفرق</td>
|
|
</tr>
|
|
<tr>
|
|
<td>FCM</td>
|
|
<td>تنبيه على الهاتف</td>
|
|
<td>أفضل تجربة فورية للمستخدم</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
|
|
<section id="payments">
|
|
<h2 class="section-title">8) الدفع والفلوس</h2>
|
|
<p>
|
|
بما أن هذا مشروع SaaS، فهو يحتاج نظام اشتراكات واضح. المستخدم يدفع، والنظام يفعّل صلاحياته تلقائياً. هذا الجزء يربط المنتج بنموذج الربح.
|
|
</p>
|
|
<div class="code-block">
|
|
<div class="code-head"><span>Webhook مثال</span><span class="dots"><span class="dot"></span><span class="dot"></span><span class="dot"></span></span></div>
|
|
<pre><code>@Post('callback')
|
|
async handlePayment(@Body() data: any) {
|
|
const isValid = this.verifySignature(data);
|
|
|
|
if (isValid && data.success) {
|
|
await this.users.activateSubscription(data.userId);
|
|
}
|
|
}</code></pre>
|
|
</div>
|
|
<ul>
|
|
<li>مزود الدفع يرسل Webhook عند نجاح العملية.</li>
|
|
<li>السيرفر يتحقق من التوقيع الرقمي.</li>
|
|
<li>إذا كانت العملية صحيحة، يفعّل الاشتراك مباشرة.</li>
|
|
</ul>
|
|
<div class="callout">
|
|
التحقق من التوقيع ليس تفصيلاً ثانوياً. إذا أُهمل، يمكن لأي جهة أن ترسل طلباً مزوراً وتفعّل اشتراكات غير حقيقية.
|
|
</div>
|
|
</section>
|
|
|
|
<section id="auth">
|
|
<h2 class="section-title">9) الأمان والاشتراكات</h2>
|
|
<p>
|
|
الأمان هنا ليس فقط تسجيل دخول. بل أيضاً حماية الميزات من الاستخدام غير المصرح به، والتحكم في حدود الخطة المجانية مقابل المدفوعة.
|
|
</p>
|
|
<h3><code>SubscriptionGuard</code> — الحارس</h3>
|
|
<div class="code-block">
|
|
<div class="code-head"><span>canActivate()</span><span class="dots"><span class="dot"></span><span class="dot"></span><span class="dot"></span></span></div>
|
|
<pre><code>async canActivate(context: ExecutionContext) {
|
|
const request = context.switchToHttp().getRequest();
|
|
const userId = request.headers['x-user-id'];
|
|
|
|
const user = await this.userRepo.findOne(userId);
|
|
|
|
if (user.tier === 'pro') return true;
|
|
|
|
if (user.requestCount < 10) {
|
|
user.requestCount++;
|
|
await this.userRepo.save(user);
|
|
return true;
|
|
}
|
|
|
|
throw new HttpException('انتهت النسخة التجريبية!', 402);
|
|
}</code></pre>
|
|
</div>
|
|
<ul>
|
|
<li>يفحص هوية المستخدم.</li>
|
|
<li>يجلب خطته الحالية من قاعدة البيانات.</li>
|
|
<li>يسمح أو يمنع الوصول حسب السياسة المقررة.</li>
|
|
</ul>
|
|
</section>
|
|
|
|
<section id="errors">
|
|
<h2 class="section-title">10) التعامل مع الأخطاء</h2>
|
|
<p>
|
|
الأخطاء ستحدث دائماً: انقطاع من Meta، توكن منتهي، مشكلة قاعدة بيانات، أو خطأ برمجي. النظام الجيد لا ينهار بصمت، بل يعيد رسالة مفهومة ومنظمة.
|
|
</p>
|
|
<div class="code-block">
|
|
<div class="code-head"><span>HttpExceptionFilter</span><span class="dots"><span class="dot"></span><span class="dot"></span><span class="dot"></span></span></div>
|
|
<pre><code>catch(exception: unknown, host: ArgumentsHost) {
|
|
const status = exception.getStatus();
|
|
const message = exception.getResponse();
|
|
|
|
this.response.status(status).json({
|
|
code: status,
|
|
timestamp: new Date().toISOString(),
|
|
error: 'حدثت مشكلة في الطلب',
|
|
detail: message
|
|
});
|
|
}</code></pre>
|
|
</div>
|
|
<div class="callout">
|
|
وجود Error Filter موحّد يجعل الـ API احترافيًا أكثر، ويسهل على الواجهة الأمامية التعامل مع الأخطاء بطريقة متسقة.
|
|
</div>
|
|
</section>
|
|
|
|
<section id="entities">
|
|
<h2 class="section-title">11) الجداول والبيانات</h2>
|
|
<p>
|
|
لا يوجد مشروع Back-end حقيقي من دون طبقة بيانات واضحة. الكيانات أو الـ Entities هي الصياغة التي نستخدمها لوصف الجداول داخل قاعدة البيانات.
|
|
</p>
|
|
<div class="code-block">
|
|
<div class="code-head"><span>User Entity</span><span class="dots"><span class="dot"></span><span class="dot"></span><span class="dot"></span></span></div>
|
|
<pre><code>@Entity('users')
|
|
export class User {
|
|
@PrimaryGeneratedColumn('uuid')
|
|
id: string;
|
|
|
|
@Column({ unique: true })
|
|
email: string;
|
|
|
|
@Column({ default: 'free' })
|
|
subscriptionTier: string;
|
|
|
|
@Column({ default: 0 })
|
|
requestCount: number;
|
|
|
|
@CreateDateColumn()
|
|
createdAt: Date;
|
|
}</code></pre>
|
|
</div>
|
|
<h3>لماذا هذا مهم؟</h3>
|
|
<ul>
|
|
<li>يوضح ما الذي نخزنه لكل مستخدم.</li>
|
|
<li>يسهل إنشاء الجداول تلقائياً عبر ORM.</li>
|
|
<li>يجعل العلاقة بين الكود والبيانات واضحة وقابلة للصيانة.</li>
|
|
</ul>
|
|
</section>
|
|
|
|
<section id="glossary">
|
|
<h2 class="section-title">12) قاموس المصطلحات</h2>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>المصطلح</th>
|
|
<th>المعنى المبسط</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>API</td>
|
|
<td>النافذة التي يتواصل عبرها النظام مع العالم الخارجي</td>
|
|
</tr>
|
|
<tr>
|
|
<td>JSON</td>
|
|
<td>صيغة نصية منظمة لتبادل البيانات</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Endpoint</td>
|
|
<td>رابط أو مسار لميزة محددة داخل الـ API</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Database</td>
|
|
<td>الخزنة التي نحفظ فيها المستخدمين والاشتراكات والقواعد</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Environment Variables</td>
|
|
<td>إعدادات وأسرار تحفظ خارج الكود داخل ملف .env</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Dependency Injection</td>
|
|
<td>طريقة NestJS الذكية لتمرير الخدمات بدون تعقيد يدوي</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
|
|
<section id="journey">
|
|
<h2 class="section-title">13) ملخص رحلة البيانات (Data Journey)</h2>
|
|
<p>هذا القسم يلخص ما يحدث تقنياً منذ لحظة ضغط المستخدم على الزر وحتى ظهور النتيجة أمامه.</p>
|
|
<div class="journey">
|
|
<div class="journey-item"><strong>1.</strong> المستخدم يضغط على زر تحليل حملة Meta من الواجهة.</div>
|
|
<div class="journey-item"><strong>2.</strong> Controller يستقبل الطلب ويحدد الخدمة المناسبة.</div>
|
|
<div class="journey-item"><strong>3.</strong> SubscriptionGuard يفحص هل يملك المستخدم صلاحية الوصول.</div>
|
|
<div class="journey-item"><strong>4.</strong> MetaAdsService يتحدث مع Meta ويجلب البيانات الخام.</div>
|
|
<div class="journey-item"><strong>5.</strong> MetaNormalizer ينظف البيانات ويحولها لصيغة موحدة.</div>
|
|
<div class="journey-item"><strong>6.</strong> AnalyticsService يفحص المؤشرات ويكتشف المشاكل.</div>
|
|
<div class="journey-item"><strong>7.</strong> AiService يضيف طبقة تفسير ذكية وتوصيات بشرية.</div>
|
|
<div class="journey-item"><strong>8.</strong> RuleEngine يفحص إن كانت هناك قواعد يجب تنفيذها.</div>
|
|
<div class="journey-item"><strong>9.</strong> NotificationService يرسل تنبيهاً إذا لزم الأمر.</div>
|
|
<div class="journey-item"><strong>10.</strong> يعيد السيرفر رداً منظماً بصيغة JSON للواجهة.</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="extras">
|
|
<h2 class="section-title">14) تفاصيل إضافية مهمة لفهم المشروع بعمق</h2>
|
|
<h3>أ) ما الذي يجعل هذا المشروع احترافيًا؟</h3>
|
|
<ul>
|
|
<li>الفصل الواضح بين الطبقات: Controller / Service / Entity / Guard.</li>
|
|
<li>إمكانية التوسعة: إضافة TikTok أو Google Ads لاحقاً دون تخريب المعمارية.</li>
|
|
<li>وجود أتمتة وتنبيهات، وليس مجرد عرض للبيانات.</li>
|
|
<li>إدارة اشتراكات فعلية، ما يجعله SaaS قابلاً للبيع.</li>
|
|
</ul>
|
|
|
|
<h3>ب) ما الذي يمكنك تحسينه لاحقاً؟</h3>
|
|
<div class="grid-2">
|
|
<div class="card">
|
|
<h4>تحسينات هندسية</h4>
|
|
<ul>
|
|
<li>إضافة Redis للـ Cache وتقليل استدعاءات Meta.</li>
|
|
<li>استخدام Queue jobs للمهام الثقيلة.</li>
|
|
<li>إضافة Logger احترافي مثل Winston أو Pino.</li>
|
|
<li>كتابة Unit Tests وIntegration Tests.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="card">
|
|
<h4>تحسينات منتجية</h4>
|
|
<ul>
|
|
<li>خطط اشتراك متعددة مع Limits واضحة.</li>
|
|
<li>واجهة Dashboard متقدمة ورسوم بيانية أكثر عمقاً.</li>
|
|
<li>تقارير PDF أو أسبوعية للمستخدمين.</li>
|
|
<li>دعم أكثر من شبكة إعلانية.</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<h3>ج) لماذا هذا المشروع ممتاز للتعلّم؟</h3>
|
|
<p>
|
|
لأنه لا يعلمك فقط كيف تكتب Controller أو Service، بل يعلمك كيف تفكر كنظام: كيف تُدخل البيانات، كيف تنظفها، كيف تحللها، كيف تحميها، كيف تربطها بالدفع، وكيف تجعلها تنتج قيمة تجارية فعلية.
|
|
</p>
|
|
</section>
|
|
|
|
<section id="closing">
|
|
<h2 class="section-title">15) الخلاصة العملية</h2>
|
|
<p>
|
|
هذا المشروع مثال ممتاز على Backend حقيقي مبني بعقلية منتج SaaS، وليس مجرد تدريب أكاديمي. إذا فهمت هذا المستند جيداً، فأنت لم تتعلم NestJS فقط، بل بدأت تفهم فعلياً كيف تُبنى الأنظمة التي تجمع البيانات، تحللها، تتخذ القرار، وتحول كل ذلك إلى خدمة قابلة للبيع والاشتراك.
|
|
</p>
|
|
<div class="callout">
|
|
أهم نقطة: لا تتعامل مع هذا المشروع ككتلة كبيرة مرعبة. قسّمه إلى موديولات صغيرة، وافهم كل جزء لوحده، ثم اربط الأجزاء معاً. بهذه الطريقة يصبح المشروع واضحاً جداً.
|
|
</div>
|
|
<p class="muted">
|
|
تم تنسيق هذه النسخة كصفحة HTML تعليمية أنيقة، باتجاه من اليمين إلى اليسار، وبأقسام واضحة وأمثلة كود داخل صناديق منظمة لسهولة القراءة والمراجعة.
|
|
</p>
|
|
</section>
|
|
</main>
|
|
</div>
|
|
|
|
<footer>
|
|
<div class="container">SaaS Meta Ads Guide — HTML Educational Edition</div>
|
|
</footer>
|
|
</body>
|
|
</html>
|