كورس مكثفة GitHub + Angular +NestJs

مقدمة Git & GitHub

مقدمة في Git & GitHub

Git هو نظام تحكم في النسخ (Version Control System) يساعد المطورين على تتبع التغييرات في ملفات الكود والتعاون مع الآخرين. GitHub هو منصة تستضيف مشاريع Git على الإنترنت وتوفر أدوات للتعاون بين المطورين.

الأوامر الأساسية في Git

1. git init - بدء مشروع جديد

ينشئ هذا الأمر مستودع Git جديد في المجلد الحالي ويبدأ تتبع التغييرات.

git init

بعد تنفيذ هذا الأمر، سيقوم Git بإنشاء مجلد خفي باسم .git لتخزين كل معلومات التحكم في النسخ.

2. git add - إضافة ملفات للتتبع

يضيف التغييرات في ملفات العمل إلى منطقة الانتظار (Staging Area) استعدادًا لتنفيذ commit.

git add اسم_الملف # لإضافة ملف معين
git add . # لإضافة جميع الملفات

3. git commit - حفظ التغييرات

يحفظ التغييرات الموجودة في منطقة الانتظار بشكل دائم في سجل Git مع رسالة وصفية.

git commit -m "رسالة توضح التغييرات"

كل commit يمثل نسخة من المشروع في تلك اللحظة، ويمكن العودة إليها لاحقًا إذا لزم الأمر.

4. git push - رفع التغييرات إلى GitHub

يرفع التغييرات المحفوظة (commits) من المستودع المحلي إلى مستودع بعيد مثل GitHub.

git push origin main

قبل استخدام push لأول مرة، يجب ربط المستودع المحلي بالمستودع البعيد باستخدام:

git remote add origin رابط_المستودع_على_GitHub

GitHub الأساسيات

إنشاء مستودع جديد على GitHub

لإنشاء مستودع جديد على GitHub:

  1. تسجيل الدخول إلى حساب GitHub الخاص بك
  2. النقر على زر "+" في الزاوية العلوية اليمنى واختيار "New repository"
  3. إدخال اسم للمستودع (يفضل أن يكون وصفيًا)
  4. اختيار بين مستودع عام (public) أو خاص (private)
  5. اختيار إنشاء ملف README.md (مفضل للمشاريع الجديدة)
  6. النقر على زر "Create repository"

ملف README.md مهم لشرح مشروعك، ويظهر تلقائيًا في صفحة المستودع الرئيسية.

العمل مع الفروع (Branches)

الفروع تسمح لك بالعمل على ميزات أو إصلاحات منفصلة دون التأثير على الفرع الرئيسي (main/master).

إنشاء فرع جديد:

git branch اسم_الفرع # إنشاء فرع جديد
git checkout اسم_الفرع # الانتقال إلى الفرع الجديد
git checkout -b اسم_الفرع # إنشاء والانتقال للفرع مباشرة

أوامر أخرى للفروع:

git branch # عرض جميع الفروع
git branch -d اسم_الفرع # حذف فرع محلي
git push origin اسم_الفرع # رفع فرع إلى GitHub

من الأفضل إنشاء فرع جديد لكل ميزة أو إصلاح تعمل عليه، ثم دمجها مع الفرع الرئيسي عند الانتهاء.

فهم Pull Requests

Pull Request (PR) هو طريقة لاقتراح تغييرات على المشروع وطلب دمجها مع الفرع الرئيسي.

عملية إنشاء Pull Request:

  1. الانتقال إلى صفحة المستودع على GitHub
  2. النقر على زر "New pull request"
  3. اختيار الفرع المصدر (الذي يحتوي التغييرات) والفرع الهدف (الرئيسي عادة)
  4. إضافة عنوان ووصف واضح للتغييرات
  5. النقر على "Create pull request"
  6. انتظار المراجعة والموافقة من المالكين أو المشرفين

Pull Requests تمكن الفرق من مراجعة الكود قبل دمجه، وتسهيل المناقشات حول التغييرات.

أوامر مفيدة مع PR:

git fetch origin # جلب أحدث التغييرات
git merge origin/main # دمج التغييرات مع نسختك المحلية
git push origin اسم_الفرع # تحديث PR بعد التعديلات

حل تعارضات الدمج:

git status # عرض الملفات المتعارضة
# تعديل الملفات يدويًا لحل التعارضات
git add الملف_المعدل
git commit -m "حل التعارضات"

5. git pull - جلب التغييرات من GitHub

يجلب التغييرات من المستودع البعيد (مثل GitHub) ويدمجها مع نسختك المحلية.

git pull origin main

هذا الأمر مهم عند العمل في فريق لضمان أن لديك أحدث نسخة من الكود.

سير العمل الأساسي مع Git

  1. قم بتعديل الملفات في مجلد العمل الخاص بك
  2. git add لتحديد التغييرات التي تريد حفظها
  3. git commit لحفظ التغييرات بشكل دائم
  4. git push لرفع التغييرات إلى GitHub
  5. git pull لجلب التغييرات من الآخرين عند العمل في فريق
Git Workflow

مقدمة عن GitHub

GitHub هو منصة تستضيف مستودعات Git على الإنترنت وتوفر:

  • مكانًا لتخزين الكود ومشاركته مع الآخرين
  • أدوات للمراجعة والتعاون على الكود
  • إمكانية تتبع المشكلات (Issues) وإدارتها
  • ميزة Pull Requests لمراجعة التغييرات قبل دمجها
  • استضافة صفحات الويب (GitHub Pages)
  • التكامل مع العديد من أدوات التطوير الأخرى

لبدء استخدام GitHub، قم بإنشاء حساب مجاني على github.com ثم أنشئ مستودعًا جديدًا (New Repository) لتبدأ في رفع مشروعك.

اليوم الأول - الفترة الثالثة (18:00 - 18:30)

مقدمة Angular + TypeScript

ما هو Angular؟

Angular هو إطار عمل (Framework) لتطوير تطبيقات الويب من طرف واحد (Single Page Applications) تم تطويره بواسطة Google.

Angular يستخدم TypeScript الذي يضيف ميزات قوية مثل أنواع البيانات (Types) والكائنات (Classes) إلى JavaScript.

إنشاء مشروع Angular جديد

لإنشاء مشروع جديد، تحتاج أولاً إلى تثبيت Angular CLI عبر npm:

npm install -g @angular/cli

ثم إنشاء المشروع الجديد:

ng new اسم_المشروع

سيطلب منك CLI بعض الخيارات مثل إضافة Routing واستخدام CSS أو SCSS.

هيكلية مشروع Angular

بعد إنشاء المشروع، ستحصل على هيكل ملفات أساسي يحتوي على:

  • src/ - يحتوي على الكود الرئيسي للتطبيق
  • src/app/ - يحتوي على المكونات (Components) والخدمات (Services)
  • src/assets/ - للملفات الثابتة مثل الصور
  • src/index.html - صفحة HTML الرئيسية
  • angular.json - ملف إعدادات المشروع

إنشاء أول Component

يمكن إنشاء مكون جديد باستخدام Angular CLI:

ng generate component اسم_المكون

أو اختصاراً:

ng g c اسم_المكون

سيقوم CLI بإنشاء مجلد للمكون يحتوي على 4 ملفات:

  • ملف TypeScript (.ts) للكود
  • ملف HTML (.html) للقالب
  • ملف CSS (.css) للتنسيقات
  • ملف اختبار (.spec.ts) للاختبارات

مقدمة NestJS

ما هو NestJS؟

NestJS هو إطار عمل (Framework) لتطوير تطبيقات خادم (Server-side) فعالة وقابلة للتطوير باستخدام Node.js. يعتمد على:

  • TypeScript (يدعم JavaScript أيضًا)
  • مبادئ البرمجة الكائنية (OOP)
  • البرمجة الوظيفية (FP)
  • البرمجة التفاعلية (FRP)

المفاهيم الأساسية:

Modules

تنظيم الكود في وحدات مستقلة

Controllers

معالجة طلبات HTTP

Providers

الخدمات والمكونات القابلة للحقن

إنشاء مشروع NestJS:

أولاً، تأكد من تثبيت Nest CLI:

npm install -g @nestjs/cli

ثم إنشاء مشروع جديد:

nest new project-name

هيكلية المشروع:

  • src/ - الكود الرئيسي للتطبيق
  • src/main.ts - نقطة دخول التطبيق
  • src/app.module.ts - الوحدة الرئيسية
  • src/app.controller.ts - المتحكم الرئيسي
  • src/app.service.ts - الخدمة الرئيسية

إنشاء Controller:

لإنشاء متحكم جديد باستخدام CLI:

nest generate controller cats

مثال على متحكم بسيط:

// cats.controller.ts
import {{'{'}} Controller, Get } from '@nestjs/common';

@Controller('cats')
export class CatsController {{'{'}}
@Get()
findAll(): string {{'{'}}
return 'This action returns all cats';
}

@Get(':id')
findOne(@Param('id') id: string): string {{'{'}}
return `This action returns a #${'{'}id} cat`;
}
}

ثم تسجيل المتحكم في الوحدة (Module):

// app.module.ts
import {{'{'}} Module } from '@nestjs/common';
import {{'{'}} CatsController } from './cats/cats.controller';

@Module({{'{'}}
controllers: [CatsController],
})
export class AppModule {{'{'}} }

NestJs متقدم - بناء REST API مع قاعدة بيانات

1. إعداد قاعدة بيانات (TypeORM):

أولاً، نثبت الحزم المطلوبة:

npm install @nestjs/typeorm typeorm mysql2

ثم نضيف إعدادات قاعدة البيانات:

// app.module.ts
import {{'{'}} TypeOrmModule } from '@nestjs/typeorm';

@Module({{'{'}}
imports: [
TypeOrmModule.forRoot({{'{'}}
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'nestdb',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true, // للتنمية فقط
}),
],
})

2. إنشاء Entity:

ننشئ كيان (Entity) يمثل جدول في قاعدة البيانات:

// cats/cat.entity.ts
import {{'{'}} Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class Cat {{'{'}}
@PrimaryGeneratedColumn()
id: number;

@Column()
name: string;

@Column()
age: number;

@Column()
breed: string;
}

3. إنشاء Service:

ننشئ خدمة للتعامل مع قاعدة البيانات:

nest generate service cats
// cats/cats.service.ts
import {{'{'}} Injectable } from '@nestjs/common';
import {{'{'}} InjectRepository } from '@nestjs/typeorm';
import {{'{'}} Repository } from 'typeorm';
import {{'{'}} Cat } from './cat.entity';

@Injectable()
export class CatsService {{'{'}}
constructor(
@InjectRepository(Cat)
private catsRepository: Repository<Cat>,
) {{'{'}} }

findAll(): Promise<Cat[]> {{'{'}}
return this.catsRepository.find();
}

findOne(id: number): Promise<Cat> {{'{'}}
return this.catsRepository.findOne({{'{'}} where: {{'{'}} id } });
}

create(cat: Cat): Promise<Cat> {{'{'}}
return this.catsRepository.save(cat);
}

async update(id: number, cat: Cat): Promise<void> {{'{'}}
await this.catsRepository.update(id, cat);
}

async remove(id: number): Promise<void> {{'{'}}
await this.catsRepository.delete(id);
}
}

4. تطوير Controller كامل:

الآن نطور المتحكم ليدعم جميع عمليات CRUD:

// cats/cats.controller.ts
import {{'{'}} Controller, Get, Post, Body, Param, Put, Delete } from '@nestjs/common';
import {{'{'}} CatsService } from './cats.service';
import {{'{'}} Cat } from './cat.entity';

@Controller('cats')
export class CatsController {{'{'}}
constructor(private readonly catsService: CatsService) {{'{'}} }

@Get()
findAll(): Promise<Cat[]> {{'{'}}
return this.catsService.findAll();
}

@Get(':id')
findOne(@Param('id') id: number): Promise<Cat> {{'{'}}
return this.catsService.findOne(id);
}

@Post()
create(@Body() cat: Cat): Promise<Cat> {{'{'}}
return this.catsService.create(cat);
}

@Put(':id')
update(@Param('id') id: number, @Body() cat: Cat): Promise<void> {{'{'}}
return this.catsService.update(id, cat);
}

@Delete(':id')
remove(@Param('id') id: number): Promise<void> {{'{'}}
return this.catsService.remove(id);
}
}

5. تسجيل الكل في الوحدة:

// cats/cats.module.ts
import {{'{'}} Module } from '@nestjs/common';
import {{'{'}} TypeOrmModule } from '@nestjs/typeorm';
import {{'{'}} CatsController } from './cats.controller';
import {{'{'}} CatsService } from './cats.service';
import {{'{'}} Cat } from './cat.entity';

@Module({{'{'}}
imports: [TypeOrmModule.forFeature([Cat])],
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {{'{'}} }

الآن لديك REST API كامل مع عمليات CRUD متصلة بقاعدة بيانات MySQL!

التكامل بين Angular و NestJs

1. إعداد HttpClientModule:

أولاً، نستورد HttpClientModule في الوحدة الرئيسية:

// app.module.ts
import {{'{'}} HttpClientModule } from '@angular/common/http';

@NgModule({{'{'}}
imports: [HttpClientModule]
})

2. إنشاء Service للاتصال بالخادم:

ng generate service api
// api.service.ts
import {{'{'}} Injectable } from '@angular/core';
import {{'{'}} HttpClient } from '@angular/common/http';
import {{'{'}} Observable } from 'rxjs';

@Injectable({{'{'}}
providedIn: 'root'
})
export class ApiService {{'{'}}
private apiUrl = 'http://localhost:3000'; // عنوان NestJs

constructor(private http: HttpClient) {{'{'}} }

getCats(): Observable<any> {{'{'}}
return this.http.get(`${'{'}this.apiUrl}/cats`);
}

getCat(id: number): Observable<any> {{'{'}}
return this.http.get(`${'{'}this.apiUrl}/cats/${'{'}id}`);
}

addCat(cat: any): Observable<any> {{'{'}}
return this.http.post(`${'{'}this.apiUrl}/cats`, cat);
}

updateCat(id: number, cat: any): Observable<any> {{'{'}}
return this.http.put(`${'{'}this.apiUrl}/cats/${'{'}id}`, cat);
}

deleteCat(id: number): Observable<any> {{'{'}}
return this.http.delete(`${'{'}this.apiUrl}/cats/${'{'}id}`);
}
}

3. استخدام Service في Component:

// cats.component.ts
import {{'{'}} Component, OnInit } from '@angular/core';
import {{'{'}} ApiService } from '../api.service';

@Component({{'{'}}
selector: 'app-cats',
templateUrl: './cats.component.html'
})
export class CatsComponent implements OnInit {{'{'}}
cats: any[] = [];
error: string = '';

constructor(private apiService: ApiService) {{'{'}} }

ngOnInit() {{'{'}}
this.loadCats();
}

loadCats() {{'{'}}
this.apiService.getCats().subscribe(
(data) => this.cats = data,
(error) => this.error = 'حدث خطأ أثناء جلب البيانات'
);
}

addCat(cat: any) {{'{'}}
this.apiService.addCat(cat).subscribe(
() => this.loadCats(),
(error) => this.error = 'حدث خطأ أثناء إضافة القط'
);
}
}

4. التعامل مع الأخطاء بشكل متقدم:

// error-interceptor.ts
import {{'{'}} Injectable } from '@angular/core';
import {{'{'}} HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http';
import {{'{'}} Observable, throwError } from 'rxjs';
import {{'{'}} catchError } from 'rxjs/operators';

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {{'{'}}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {{'{'}}
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {{'{'}}
let errorMsg = '';
if (error.error instanceof ErrorEvent) {{'{'}}
errorMsg = `خطأ في العميل: ${'{'}error.error.message}`;
} else {{'{'}}
errorMsg = `خطأ في الخادم: ${'{'}error.status}, ${'{'}error.message}`;
}
console.error(errorMsg);
return throwError(errorMsg);
})
);
}
}

ثم تسجيل الـ Interceptor في الوحدة الرئيسية:

// app.module.ts
import {{'{'}} HTTP_INTERCEPTORS } from '@angular/common/http';
import {{'{'}} ErrorInterceptor } from './error-interceptor';

@NgModule({{'{'}}
providers: [
{{'{'}} provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }
]
})

الآن لديك تطبيق Angular متكامل مع NestJs يدعم عرض البيانات، إرسال الطلبات، والتعامل مع الأخطاء!

مراجعة عامة وأسئلة شائعة

نصائح GitHub

  • استخدم رسائل commit واضحة ووصفية
  • أنشئ فرعًا جديدًا لكل ميزة أو إصلاح (feature/bugfix branches)
  • اجعل pull requests صغيرة ومركزة على مهمة واحدة
  • استخدم .gitignore لاستبعاد الملفات غير الضرورية

نصائح Angular

  • استخدم lazy loading لتحسين أداء التطبيق
  • افصل المكونات الكبيرة إلى مكونات أصغر وأكثر تخصصًا
  • استخدم services لمشاركة البيانات بين المكونات
  • استفد من RxJS للتعامل مع البيانات غير المتزامنة

نصائح NestJS

  • استخدم DTOs (Data Transfer Objects) للتحقق من صحة بيانات الإدخال
  • استفد من interceptors للتعامل مع الأخطاء بشكل مركزي
  • استخدم guards لحماية نقاط نهاية API
  • فصل منطق الأعمال عن controllers باستخدام services

أسئلة شائعة

كيف أتعامل مع تعارضات الدمج في Git؟

استخدم git status لتحديد الملفات المتعارضة، ثم عدلها يدويًا لحل التعارضات، وأخيرًا أعد commit التغييرات.

ما الفرق بين Angular و NestJS؟

Angular هو إطار عمل للواجهة الأمامية (Frontend) بينما NestJS هو إطار عمل للخلفية (Backend). يمكن دمجهما معًا لبناء تطبيقات كاملة.

كيف يمكن تحسين أداء تطبيق Angular؟

استخدم ChangeDetectionStrategy.OnPush، و lazy loading للوحدات، وتجنب عمليات DOM المباشرة، واستفد من trackBy مع ngFor.

Forms والتنقل بين الصفحات (Routing)

Template-driven Forms:

لاستخدام النماذج القائمة على القوالب، يجب استيراد FormsModule:

import {{'{'}} FormsModule } from '@angular/forms';

@NgModule({{'{'}}
imports: [FormsModule]
})

مثال على نموذج:

<form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)">
<input name="username" ngModel required>
<input name="email" ngModel required email>
<button type="submit">Submit</button>
</form>

Reactive Forms:

لاستخدام النماذج التفاعلية، يجب استيراد ReactiveFormsModule:

import {{'{'}} ReactiveFormsModule, FormGroup, FormControl } from '@angular/forms';

@NgModule({{'{'}}
imports: [ReactiveFormsModule]
})

مثال على نموذج تفاعلي:

// في الكلاس
myForm = new FormGroup({{'{'}}
username: new FormControl(''),
email: new FormControl('')
});

// في القالب
<form [formGroup]="myForm" (ngSubmit)="onSubmit()">
<input formControlName="username">
<input formControlName="email">
<button type="submit">Submit</button>
</form>

Routing بين الصفحات:

لإعداد Routing في Angular:

// app-routing.module.ts
import {{'{'}} NgModule } from '@angular/core';
import {{'{'}} RouterModule, Routes } from '@angular/router';
import {{'{'}} HomeComponent } from './home/home.component';
import {{'{'}} AboutComponent } from './about/about.component';

const routes: Routes = [
{{'{'}} path: '', component: HomeComponent },
{{'{'}} path: 'about', component: AboutComponent },
{{'{'}} path: '**', redirectTo: '' } // صفحة 404
];

@NgModule({{'{'}}
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {{'{'}} }

استخدام الروابط في القالب:

<nav>
<a routerLink="/">Home</a>
<a routerLink="/about">About</a>
</nav>

<router-outlet></router-outlet>

الانتقال برمجيًا:

import {{'{'}} Router } from '@angular/router';

constructor(private router: Router) {{'{'}} }

navigateToAbout() {{'{'}}
this.router.navigate(['/about']);
}

Data Binding والمكونات (Components)

أنواع Data Binding:

Interpolation ({{'{{'}} }})

عرض بيانات من الكود في القالب:

<p>{{'{{'}} title }}</p>
Property Binding [ ]

ربط خاصية عنصر بقيمة من الكود:

<img [src]="imageUrl">
Event Binding ( )

ربط حدث عنصر بدالة في الكود:

<button (click)="onClick()">Click</button>
Two-Way Binding [( )]

ربط ثنائي الاتجاه مع النماذج:

<input [(ngModel)]="username">

المكونات (Components):

المكونات هي اللبنات الأساسية في Angular. كل مكون يتكون من:

  • فئة TypeScript تحتوي على البيانات والمنطق
  • قالب HTML يعرض الواجهة
  • تنسيقات CSS خاصة بالمكون

مثال على مكون بسيط:

// app.component.ts
@Component({{'{'}}
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {{'{'}}
title = 'My App';
count = 0;

increment() {{'{'}}
this.count++;
}
}

الخدمات (Services):

الخدمات تستخدم لمشاركة البيانات والمنطق بين المكونات. لإنشاء خدمة:

ng generate service اسم_الخدمة

مثال على خدمة بسيطة:

// data.service.ts
@Injectable({{'{'}}
providedIn: 'root'
})
export class DataService {{'{'}}
private data: any[] = [];

getData() {{'{'}}
return this.data;
}

addData(item: any) {{'{'}}
this.data.push(item);
}
}

لحقن الخدمة في مكون:

constructor(private dataService: DataService) {{'{'}} }

© 2023 مقدمة Git & GitHub و Angular. جميع الحقوق محفوظة.

خطة كورس GitHub + Angular + NestJs

خطة كورس مكثفة

GitHub Angular NestJs

الكورس لمدة 3 أيام، كل يوم من 16:30 حتى 18:30 (ساعتان يوميًا)، بإجمالي 6 ساعات فقط

اليوم الأول

Git & GitHub

  • 16:30 - 17:15

    مقدمة Git & GitHub

    تعريف Git, الأوامر الأساسية (init, add, commit, push, pull)

  • 17:15 - 18:00

    GitHub الأساسيات

    إنشاء مستودع جديد، عمل فروع (git branch/checkout)، دمج التغييرات (git merge)، فهم Pull Requests وعملية المراجعة

Angular

  • 18:00 - 18:30

    مقدمة Angular + TypeScript

    إنشاء مشروع جديد، هيكلية المشروع، أول Component

اليوم الثاني

Angular متقدم

  • 16:30 - 17:15

    Data Binding و Components

    Data Binding, Components, خدمات Services

  • 17:15 - 18:00

    Forms و Routing

    التعامل مع النماذج والتنقل بين الصفحات

NestJs

  • 18:00 - 18:30

    مقدمة NestJs

    المفاهيم الأساسية، إنشاء مشروع، Controllers

اليوم الثالث

NestJs متقدم

  • 16:30 - 17:15

    بناء REST API

    بناء REST API بسيط CRUD وربطه بقاعدة بيانات

التكامل بين Angular و NestJs

  • 17:15 - 18:00

    ربط Angular مع NestJs

    عرض البيانات، إرسال الطلبات، التعامل مع الأخطاء

ختام الكورس

  • 18:00 - 18:30

    مراجعة عامة

    أسئلة وأجوبة + نصائح استخدام GitHub, Angular, NestJs

ملخص الكورس

GitHub

  • الأوامر الأساسية في Git
  • إنشاء وإدارة الفروع (Branches)
  • عمل Pull Requests ومراجعتها
  • حل تعارضات الدمج (Merge Conflicts)

Angular

  • إنشاء Components و Services
  • Data Binding و Forms
  • Routing بين الصفحات

NestJs

  • إنشاء REST API
  • Controllers و Services
  • التكامل مع Angular

© 2023 خطة كورس GitHub + Angular + NestJs. جميع الحقوق محفوظة.

تعليقات

المشاركات الشائعة من هذه المدونة

ngx-extended-pdf-viewer

how to getting access token for https://fcm.googleapis.com/v1/projects/al-ayahya-co/messages:send for angular and backend nestjs

طريقة تفعيل زر Inline Keyboard في Telegram Bot