هبدأ معاك بالخطوات التفصيلية واحدة واحدة من أول البداية، مع شرح طريقة التفكير في كل خطوة:
Step 1: Understanding and Requirements Analysis (3-5 Days) 📋
1. قراءة وتحليل الـ PRD
التنفيذ:
- خد ورقة وقلم واقرأ الـ PRD كاملاً مرتين على الأقل
- حط علامة على كل جملة فيها وصف لـ feature أو requirement
- اعمل قائمة بالـ requirements الواضحة والغير واضحة
طريقة التفكير:
لازم أفهم المشروع كويس قبل ما أبدأ في الـ implementation. عندي نظام بيربط بين الـ travelers ووكالات السفر.
من خلال الـ PRD، أقدر أحدد:
- المشكلة اللي الـ product بيحلها (صعوبة إيجاد وحجز رحلات)
- الـ target users (المسافرين، وكالات السفر)
- نوع الـ data اللي هنتعامل معاها (رحلات، حجوزات، ملفات شخصية)
لازم أكتب كل requirement بطريقة واضحة، مثلاً:
- "نظام authentication للـ users ووكالات السفر"
- "نظام advanced search بـ filters للوجهات والتواريخ والـ budget"
- "نظام booking وإدارة الـ bookings للـ users"
2. تحديد الـ User Stories
التنفيذ:
- اكتب قائمة بالـ user stories لكل نوع user
- استخدم صيغة “كـ [user]، أريد [feature] لكي [goal/value]”
- رتب الـ stories حسب الـ priority
طريقة التفكير:
هحدد بالضبط الـ users بيعملوا إيه في النظام:
المسافرين:
- كـ traveler، أريد إنشاء account بسهولة باستخدام حساب Google لتوفير الوقت
- كـ traveler، أريد البحث عن trips باستخدام filters متعددة لإيجاد الرحلة المناسبة
- كـ traveler، أريد booking رحلة وتلقي confirmation عبر الـ email للاطمئنان
وكالات السفر:
- كـ travel agency، أريد عرض رحلاتي على الـ platform لجذب المزيد من الـ customers
- كـ travel agency، أريد تلقي notifications عند وجود bookings جديدة للرد بسرعة
- كـ travel agency، أريد رؤية الـ reviews من الـ customers لتحسين خدماتي
الـ user stories دي هتساعدني أفهم المطلوب من كل page وfeature.
3. تحديد الـ MVP (الحد الأدنى من المنتج القابل للتطبيق)
التنفيذ:
- حدد الـ features الأساسية اللي بدونها الـ product ميقدرش يشتغل
- ركز على الـ functions اللي تحل المشكلة الرئيسية
- استبعد الـ additional features للـ future releases
طريقة التفكير:
بفكر في السؤال: إيه الحاجات اللي لو مش موجودة الـ product ميبقاش ليه value؟
MVP لمشروع Hamla:
1. Authentication للـ users ووكالات السفر
2. عرض وإدارة الـ profiles البسيطة
3. إدارة الـ trips (إضافة، عرض، تعديل) للوكالات
4. الـ basic search عن الـ trips حسب المكان والتاريخ
5. نظام booking أساسي مع confirmation notifications
6. عرض الـ bookings للطرفين
حاجات ممكن تتأجل لـ future releases:
- نظام الـ recommendations المتقدم
- برامج الـ loyalty
- الـ integration مع أنظمة CRM
- الـ advanced filters
Step 2: Database Structure Design (3-4 Days) 🗄️
1. تحديد الـ Entities الرئيسية
التنفيذ:
- اكتب على ورقة كل الـ entities اللي هتكون موجودة في النظام
- حدد الـ relationships بين الـ entities (1:1، 1:n، n:m)
طريقة التفكير:
بفكر: إيه هي الـ main things اللي بنتعامل معاها؟
الـ main entities للمشروع:
1. Users (المستخدمين):
- العاديين (المسافرين)
- وكالات السفر
2. Trips (الرحلات):
- الوجهات
- التواريخ
- الأسعار
- التفاصيل
3. Bookings (الحجوزات):
- ربط بين الـ user والـ trip
- حالة الـ booking
- تفاصيل الـ payment
4. Reviews (التقييمات):
- تقييمات الـ users للوكالات
- الـ comments
5. Notifications (الإشعارات):
- إشعارات للـ users
- إشعارات للوكالات
الـ relationships:
- الـ user يمكنه عمل عدة bookings (1:n)
- الـ agency يمكنها إضافة عدة trips (1:n)
- الـ trip يمكن أن تحتوي على عدة bookings (1:n)
- الـ user يمكنه تقييم عدة agencies (n:m)
2. تصميم مخطط قاعدة البيانات
التنفيذ:
- ارسم ER Diagram يوضح الـ relationships
- حدد الـ attributes لكل entity
- حدد الـ primary keys و foreign keys
طريقة التفكير:
هصمم مخطط مفصل لكل entity مع الـ attributes بتاعته:
Collection: Users
{
_id: ObjectId (PK),
type: String (user/agency),
name: String,
email: String (unique),
password: String (hashed),
phone: String,
location: {
city: String,
country: String
},
preferences: Array (للمستخدمين فقط),
agencyDetails: Object (للوكالات فقط),
loyaltyPoints: Number (للمستخدمين فقط),
createdAt: Date
}
Collection: Trips
{
_id: ObjectId (PK),
agencyId: ObjectId (FK),
title: String,
destination: String,
startDate: Date,
endDate: Date,
price: Number,
description: String,
maxTravelers: Number,
availableSpots: Number,
images: Array,
status: String (active/inactive),
createdAt: Date
}
Collection: Bookings
{
_id: ObjectId (PK),
userId: ObjectId (FK),
tripId: ObjectId (FK),
agencyId: ObjectId (FK),
status: String (pending/confirmed/cancelled),
travelersCount: Number,
totalPrice: Number,
createdAt: Date
}
هراعي إن الـ schema يكون flexible كفاية للـ future changes.
3. تحديد الـ Indexes
التنفيذ:
- حدد الـ fields اللي هيتم البحث عليها كتير
- حدد الـ fields اللي محتاجة تكون unique
- اختار نوع الـ index المناسب (simple, compound, text, etc.)
طريقة التفكير:
بفكر في الـ common queries في المشروع:
1. البحث عن الـ users بالـ email (للـ authentication)
2. البحث عن الـ trips بالـ destination والـ date (للبحث)
3. البحث عن الـ bookings الخاصة بـ user أو agency معينة
4. البحث عن الـ trips بالـ price (للـ filtering)
الـ indexes اللي هنحتاجها:
- Users: { email: 1 } (unique)
- Trips: { destination: 1, startDate: 1 } (compound)
- Trips: { price: 1 } (للـ filtering بالسعر)
- Bookings: { userId: 1 } (للبحث عن bookings الـ user)
- Bookings: { agencyId: 1 } (للبحث عن bookings الـ agency)
Step 3: API Design and Architecture (4-5 Days) 🔌
1. تحديد الـ API Endpoints
التنفيذ:
- حدد كل الـ endpoints المطلوبة حسب الـ user stories
- اكتب description لكل endpoint (HTTP method, URL, params, response)
- قسم الـ endpoints لـ logical groups
طريقة التفكير:
بفكر في الـ operations اللي الـ user هيعملها والـ endpoints اللي هحتاجها لتنفيذها:
Authentication:
- POST /api/auth/register - تسجيل account جديد
- POST /api/auth/login - تسجيل الدخول
- POST /api/auth/register-agency - تسجيل agency جديدة
Users:
- GET /api/users/profile - عرض الـ profile للـ user
- PUT /api/users/profile - تعديل الـ profile
- GET /api/users/bookings - عرض bookings الـ user
Agencies:
- GET /api/agencies - عرض قائمة الـ agencies
- GET /api/agencies/:id - عرض تفاصيل agency محددة
- GET /api/agencies/:id/trips - عرض trips agency محددة
Trips:
- GET /api/trips - البحث عن الـ trips (مع filters)
- GET /api/trips/:id - عرض تفاصيل trip محددة
- POST /api/trips - إضافة trip جديدة (للوكالات)
- PUT /api/trips/:id - تعديل trip (للوكالات)
Bookings:
- POST /api/bookings - إنشاء booking جديد
- GET /api/bookings/:id - عرض تفاصيل booking
- PUT /api/bookings/:id/status - تغيير status الـ booking
بفكر في كل endpoint من جانب:
- إيه الـ inputs المطلوبة؟
- إيه الـ outputs المتوقعة؟
- مين مسموح له use الـ endpoint ده؟
2. تصميم هيكل البيانات للـ Requests والـ Responses
التنفيذ:
- حدد شكل الـ requests (البيانات المطلوبة)
- حدد شكل الـ responses (البيانات المرجعة)
- راعي الـ standards والـ best practices
طريقة التفكير:
هصمم الـ request و response لكل endpoint بحيث تكون consistent:
مثال:
POST /api/trips
Request:
{
"title": "رحلة إلى شرم الشيخ",
"destination": "شرم الشيخ",
"startDate": "2023-08-15",
"endDate": "2023-08-20",
"price": 2500,
"description": "استمتع برحلة لمدة 5 أيام في شرم الشيخ",
"maxTravelers": 20,
"images": ["url1", "url2"]
}
Response (Success - 201):
{
"success": true,
"data": {
"id": "trip_id",
"title": "رحلة إلى شرم الشيخ",
... (باقي البيانات)
}
}
Response (Error - 400/401/403/500):
{
"success": false,
"error": {
"code": "INVALID_DATA",
"message": "رسالة الخطأ"
}
}
هحافظ على format موحد في كل الـ endpoints عشان تسهيل استخدام الـ API.
3. تصميم آلية الـ Authentication والـ Authorization
التنفيذ:
- اختر طريقة الـ authentication (JWT, OAuth, etc.)
- حدد سياسات الـ authorization (مين يقدر يوصل لأي endpoint)
- صمم آلية الـ tokens وتجديدها
طريقة التفكير:
في المشروع ده، هنحتاج:
1. JWT Authentication:
- عمل tokens عند الـ login
- تشفير الـ passwords بـ bcrypt
- token expiration time (1 ساعة)
- refresh tokens (1 أسبوع)
2. Authorization Rules:
- Public endpoints: register, login, search trips, agency listings
- User endpoints: booking, profile management, reviews
- Agency endpoints: trip management, booking management
- Admin endpoints: user management, system configuration
3. Social Authentication:
- OAuth للـ Google و Facebook
- ربط الـ social accounts بالـ existing accounts
هيكون عندنا middleware للـ authorization يتحقق من الـ permissions قبل تنفيذ أي request.
Step 4: Development Environment Setup (2-3 Days) ⚙️
1. اختيار الـ Technology Stack
التنفيذ:
- اختر الـ backend framework (Node.js/Express, Django, etc.)
- اختر الـ database (MongoDB, PostgreSQL, etc.)
- اختر الـ frontend framework/library (React, Vue, etc.)
- حدد الـ tools للـ testing, deployment, etc.
طريقة التفكير:
بناءً على احتياجات المشروع، أنا هختار:
Backend:
- ASP.NET Core 6 (سرعة التطوير، الأداء العالي، وتكامله السلس مع Entity Framework)
- Entity Framework Core كـ ORM مع Identity Package للـ authentication
- SQL Server للـ database (قوة في العلاقات المعقدة والاستعلامات)
Frontend:
- React.js للويب (flexible، ecosystem كبير، وأداء عالي)
- React Native للموبايل (code sharing بين iOS و Android)
- Redux للـ state management
Tools:
- Git للـ version control
- Azure DevOps للـ CI/CD
- xUnit للـ testing
- Swagger للـ API documentation
- Docker للـ containerization
هذا الـ stack هيوفر:
- أداء عالي ومقياسية جيدة للـ backend
- مرونة وتجربة مستخدم سلسة في الـ frontend
- قاعدة بيانات قوية وموثوقة
- بيئة تطوير متكاملة مع أدوات CI/CD
2. إعداد Solution Structure
التنفيذ:
- إنشاء solution جديد باستخدام Visual Studio
- تنظيم المشروع بنمط الـ Clean Architecture
- إعداد الـ layers المختلفة للمشروع
طريقة التفكير:
هنظم المشروع بطريقة الـ Clean Architecture عشان:
- يبقى سهل صيانته
- يسمح بـ separation of concerns
- يسهل عمل unit testing
Solution structure:
- Hamla.API - الـ presentation layer (Web API controllers)
- Hamla.Core - الـ domain models و business logic
- Hamla.Infrastructure - الـ data access والـ external services
- Hamla.Shared - الـ DTOs والـ utilities المشتركة
- Hamla.Tests - الـ unit tests والـ integration tests
هيكل الـ folders لكل project:
Hamla.API:
/Controllers
/Middleware
/Filters
/Extensions
/Configurations
Hamla.Core:
/Entities
/Interfaces
/Services
/Specifications
/Exceptions
Hamla.Infrastructure:
/Data
/Configurations
/Migrations
/Repositories
/Identity
/Services
/Email
/Storage
/Payment
Hamla.Shared:
/DTOs
/Helpers
/Constants
/Enums
3. إعداد Repository و Source Control
التنفيذ:
- إنشاء Git repository جديد
- إعداد Git ignore file مناسب
- إعداد branching strategy للمشروع
- إعداد GitHub/Azure DevOps للإدارة
طريقة التفكير:
هستخدم Git Flow كاستراتيجية للـ branches:
- main: للـ production code
- develop: للـ development code
- feature/*: للميزات الجديدة
- bugfix/*: لإصلاح الـ bugs
- release/*: للإصدارات
هضيف gitignore مناسب لـ .NET:
- bin و obj folders
- ملفات الـ IDE (*.user, .vs/)
- app settings السرية
- packages الخارجية
هضيف أيضًا README شامل للمشروع يتضمن:
- وصف المشروع
- كيفية الإعداد والتشغيل
- هيكل المشروع
- الـ API endpoints الرئيسية
4. إعداد Database و ORM
التنفيذ:
- تثبيت Entity Framework Core packages
- إعداد ApplicationDbContext
- تكوين الـ Entity configurations
- إعداد الـ migrations
طريقة التفكير:
هبدأ بتثبيت الـ packages المطلوبة:
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Tools
- Microsoft.AspNetCore.Identity.EntityFrameworkCore
هصمم ApplicationDbContext ليدعم الـ Identity:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{ }
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<AgencyProfile> AgencyProfiles { get; set; }
public DbSet<Trip> Trips { get; set; }
public DbSet<Booking> Bookings { get; set; }
public DbSet<Review> Reviews { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Apply configurations
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
// Custom configurations
builder.Entity<Trip>()
.HasIndex(t => t.Destination);
builder.Entity<Trip>()
.HasIndex(t => t.StartDate);
}
}
// Entity configurations example
public class TripConfiguration : IEntityTypeConfiguration<Trip>
{
public void Configure(EntityTypeBuilder<Trip> builder)
{
builder.HasKey(t => t.Id);
builder.Property(t => t.Title)
.IsRequired()
.HasMaxLength(100);
builder.Property(t => t.Description)
.HasMaxLength(2000);
builder.HasOne(t => t.Agency)
.WithMany(a => a.Trips)
.HasForeignKey(t => t.AgencyId);
}
}
5. إعداد Authentication & Authorization
التنفيذ:
- تثبيت وتكوين ASP.NET Core Identity
- إعداد JWT Authentication
- تكوين الـ role-based authorization
- إعداد الـ password policies
طريقة التفكير:
هستخدم Identity مع JWT authentication:
// Setup Identity
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = true;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
options.Lockout.MaxFailedAccessAttempts = 5;
// User settings
options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Setup JWT Authentication
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["JWT:Issuer"],
ValidAudience = Configuration["JWT:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(Configuration["JWT:Secret"]))
};
});
// Setup Authorization Policies
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdminRole", policy =>
policy.RequireRole("Admin"));
options.AddPolicy("RequireAgencyRole", policy =>
policy.RequireRole("Agency"));
options.AddPolicy("RequireTravelerRole", policy =>
policy.RequireRole("Traveler"));
});
6. إعداد الـ Dependency Injection
التنفيذ:
- تسجيل الـ services والـ repositories
- إعداد interface-implementation patterns
- تكوين الـ scoped و transient و singleton services
طريقة التفكير:
هنظم الـ DI بطريقة مرتبة:
// استخدام extension methods لتنظيم الـ service registration
public static class DependencyInjection
{
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{
// Repositories
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<ITripRepository, TripRepository>();
services.AddScoped<IBookingRepository, BookingRepository>();
services.AddScoped<IReviewRepository, ReviewRepository>();
// Services
services.AddScoped<IAuthService, AuthService>();
services.AddScoped<ITripService, TripService>();
services.AddScoped<IBookingService, BookingService>();
services.AddScoped<IReviewService, ReviewService>();
services.AddScoped<INotificationService, NotificationService>();
// Utilities
services.AddSingleton<ICacheService, RedisCacheService>();
services.AddTransient<IEmailService, EmailService>();
services.AddTransient<IImageService, AzureBlobImageService>();
return services;
}
public static IServiceCollection AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration)
{
// Database
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));
// Redis Cache
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = configuration.GetConnectionString("RedisConnection");
options.InstanceName = "HamlaCache_";
});
// Other infrastructure services
return services;
}
}
// في Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddApplicationServices();
builder.Services.AddInfrastructureServices(builder.Configuration);
7. إعداد الـ API Configurations
التنفيذ:
- تكوين الـ controllers و routes
- إعداد الـ API versioning
- تكوين الـ CORS policy
- إعداد الـ validation و model binding
طريقة التفكير:
هكون الـ API بطريقة احترافية:
// Controller conventions
services.AddControllers(options =>
{
// Add filters
options.Filters.Add(new ValidateModelAttribute());
options.Filters.Add(new ApiExceptionFilter());
})
.AddJsonOptions(options =>
{
// Configure JSON serialization
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});
// API Versioning
services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = ApiVersionReader.Combine(
new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("X-API-Version"));
});
// CORS
services.AddCors(options =>
{
options.AddPolicy("AllowedOrigins", policy =>
{
policy.WithOrigins(
"https://hamla-app.com",
"https://admin.hamla-app.com")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
// FluentValidation
services.AddFluentValidationAutoValidation();
services.AddValidatorsFromAssembly(typeof(Program).Assembly);
8. إعداد الـ Testing Environment
التنفيذ:
- إنشاء مشروع للـ unit tests
- إنشاء مشروع للـ integration tests
- إعداد الـ test fixtures و helpers
- إعداد الـ mock data للاختبارات
طريقة التفكير:
هعد بيئة اختبار متكاملة:
// Unit Tests Project Structure
Hamla.Tests.Unit
/Controllers
/Services
/Repositories
/Helpers
// Integration Tests Project Structure
Hamla.Tests.Integration
/Api
/Database
/Fixtures
/Helpers
// Example Test Class
public class TripServiceTests
{
private readonly Mock<ITripRepository> _mockTripRepository;
private readonly Mock<IMapper> _mockMapper;
private readonly TripService _tripService;
public TripServiceTests()
{
_mockTripRepository = new Mock<ITripRepository>();
_mockMapper = new Mock<IMapper>();
_tripService = new TripService(
_mockTripRepository.Object,
_mockMapper.Object);
}
[Fact]
public async Task GetTripByIdAsync_WithValidId_ReturnsTripDto()
{
// Arrange
var tripId = 1;
var trip = new Trip { Id = tripId, Title = "Test Trip" };
var tripDto = new TripDto { Id = tripId, Title = "Test Trip" };
_mockTripRepository.Setup(r => r.GetByIdAsync(tripId))
.ReturnsAsync(trip);
_mockMapper.Setup(m => m.Map<TripDto>(trip))
.Returns(tripDto);
// Act
var result = await _tripService.GetTripByIdAsync(tripId);
// Assert
Assert.NotNull(result);
Assert.Equal(tripId, result.Id);
Assert.Equal("Test Trip", result.Title);
}
}
// WebApplicationFactory for Integration Tests
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Remove the app's ApplicationDbContext registration
var descriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(DbContextOptions<ApplicationDbContext>));
if (descriptor != null)
{
services.Remove(descriptor);
}
// Add ApplicationDbContext using an in-memory database for testing
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDbForTesting");
});
// Seed test data
var sp = services.BuildServiceProvider();
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<ApplicationDbContext>();
db.Database.EnsureCreated();
// Add test data
SeedTestData(db);
}
});
}
private void SeedTestData(ApplicationDbContext context)
{
// Add test data here
}
}
9. إعداد الـ Development Tool
التنفيذ:
- تكوين الـ Swagger/OpenAPI للتوثيق
- إعداد الـ logging system
- تكوين الـ code analysis و linting tools
- إعداد الـ debugging tools
طريقة التفكير:
هكمل إعداد أدوات التطوير المساعدة:
// تكوين Swagger للتوثيق
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Hamla API",
Version = "v1",
Description = "API for Hamla Travel Platform",
Contact = new OpenApiContact
{
Name = "Hamla Development Team",
Email = "dev@hamla-travel.com",
Url = new Uri("https://hamla-travel.com")
}
});
// إضافة التعليقات XML للتوثيق
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
// إعداد الـ security scheme للـ authentication
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
});
// إعداد نظام الـ logging باستخدام Serilog
builder.Host.UseSerilog((context, services, configuration) => configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File("logs/hamla-.log", rollingInterval: RollingInterval.Day)
.WriteTo.ApplicationInsights(
services.GetRequiredService<TelemetryConfiguration>(),
TelemetryConverter.Traces));
// تكوين أدوات Code Analysis
services.AddCodeAnalysis(options =>
{
options.EnableStyleCopAnalyzers = true;
options.EnableSonarAnalyzers = true;
});
// تمكين أدوات العرض والتصحيح للمطورين
if (builder.Environment.IsDevelopment())
{
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDiagnostics();
}
10. إعداد Docker و Containerization
التنفيذ:
- إنشاء Dockerfile للمشروع
- إعداد docker-compose للتطوير المحلي
- تكوين multi-stage builds لتحسين الأداء
طريقة التفكير:
هعمل إعداد Docker لتسهيل التطوير والنشر:
// Dockerfile للمشروع
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["Hamla.API/Hamla.API.csproj", "Hamla.API/"]
COPY ["Hamla.Core/Hamla.Core.csproj", "Hamla.Core/"]
COPY ["Hamla.Infrastructure/Hamla.Infrastructure.csproj", "Hamla.Infrastructure/"]
COPY ["Hamla.Shared/Hamla.Shared.csproj", "Hamla.Shared/"]
RUN dotnet restore "Hamla.API/Hamla.API.csproj"
COPY . .
WORKDIR "/src/Hamla.API"
RUN dotnet build "Hamla.API.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "Hamla.API.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Hamla.API.dll"]
// docker-compose.yml للتطوير المحلي
version: '3.8'
services:
api:
build:
context: .
dockerfile: Dockerfile
ports:
- "5000:80"
- "5001:443"
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=https://+:443;http://+:80
- ASPNETCORE_Kestrel__Certificates__Default__Password=YourPassword
- ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx
- ConnectionStrings__DefaultConnection=Server=db;Database=Hamla;User=sa;Password=${DB_PASSWORD};
- ConnectionStrings__RedisConnection=redis:6379
depends_on:
- db
- redis
volumes:
- ~/.aspnet/https:/https:ro
db:
image: mcr.microsoft.com/mssql/server:2019-latest
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=${DB_PASSWORD}
ports:
- "1433:1433"
volumes:
- hamla-data:/var/opt/mssql
redis:
image: redis:alpine
ports:
- "6379:6379"
volumes:
- hamla-redis:/data
volumes:
hamla-data:
hamla-redis:
11. إعداد CI/CD Pipeline
التنفيذ:
- إعداد GitHub Actions أو Azure DevOps
- تكوين workflows للـ build, test, deploy
- إنشاء environments للـ development, staging, production
طريقة التفكير:
هصمم pipeline مستمر للـ CI/CD:
// GitHub Actions workflow file (.github/workflows/build-and-deploy.yml)
name: Build and Deploy
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore --configuration Release
- name: Test
run: dotnet test --no-build --verbosity normal --configuration Release
- name: Publish
if: github.event_name != 'pull_request'
run: dotnet publish -c Release -o ${{env.DOTNET_ROOT}}/publish
- name: Upload artifact
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v2
with:
name: app
path: ${{env.DOTNET_ROOT}}/publish
deploy-to-development:
if: github.ref == 'refs/heads/develop' && github.event_name != 'pull_request'
needs: build
runs-on: ubuntu-latest
environment:
name: Development
url: https://dev-api.hamla-travel.com
steps:
- name: Download artifact
uses: actions/download-artifact@v2
with:
name: app
path: ./publish
- name: Deploy to Azure Web App
uses: azure/webapps-deploy@v2
with:
app-name: 'hamla-api-dev'
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE_DEV }}
package: ./publish
deploy-to-production:
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
needs: build
runs-on: ubuntu-latest
environment:
name: Production
url: https://api.hamla-travel.com
steps:
- name: Download artifact
uses: actions/download-artifact@v2
with:
name: app
path: ./publish
- name: Deploy to Azure Web App
uses: azure/webapps-deploy@v2
with:
app-name: 'hamla-api-prod'
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE_PROD }}
package: ./publish
12. إعداد Environment Variables وتكوين الـ Settings
التنفيذ:
- إعداد ملفات appsettings لمختلف البيئات
- إعداد الـ secret management
- تكوين قراءة الـ environment variables
طريقة التفكير:
هنظم إعدادات التطبيق بشكل محكم:
// هيكل ملفات appsettings
- appsettings.json (إعدادات عامة)
- appsettings.Development.json (إعدادات بيئة التطوير)
- appsettings.Staging.json (إعدادات بيئة الاختبار)
- appsettings.Production.json (إعدادات بيئة الإنتاج)
// مثال على محتوى appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "PLACEHOLDER_TO_BE_REPLACED",
"RedisConnection": "PLACEHOLDER_TO_BE_REPLACED"
},
"JWT": {
"Secret": "PLACEHOLDER_SECRET_TO_BE_REPLACED",
"Issuer": "HamlaAPI",
"Audience": "HamlaClients",
"DurationInMinutes": 60
},
"Email": {
"SendGrid": {
"ApiKey": "PLACEHOLDER_API_KEY",
"FromEmail": "noreply@hamla-travel.com",
"FromName": "Hamla Travel"
}
},
"Storage": {
"Azure": {
"ConnectionString": "PLACEHOLDER_CONNECTION_STRING",
"ContainerName": "hamla-images"
}
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
// تكوين قراءة الـ environment variables في Program.cs
// يتم استبدال قيم الـ placeholders بقيم من الـ environment variables
var builder = WebApplication.CreateBuilder(args);
// تجاوز الإعدادات من الـ environment variables
if (!builder.Environment.IsDevelopment())
{
builder.Configuration["ConnectionStrings:DefaultConnection"] =
Environment.GetEnvironmentVariable("DB_CONNECTION_STRING");
builder.Configuration["ConnectionStrings:RedisConnection"] =
Environment.GetEnvironmentVariable("REDIS_CONNECTION_STRING");
builder.Configuration["JWT:Secret"] =
Environment.GetEnvironmentVariable("JWT_SECRET");
builder.Configuration["Email:SendGrid:ApiKey"] =
Environment.GetEnvironmentVariable("SENDGRID_API_KEY");
builder.Configuration["Storage:Azure:ConnectionString"] =
Environment.GetEnvironmentVariable("AZURE_STORAGE_CONNECTION_STRING");
}
// إدارة الـ secrets في بيئة التطوير
if (builder.Environment.IsDevelopment())
{
builder.Configuration.AddUserSecrets<Program>();
}
13. إعداد Monitoring والـ Logging
التنفيذ:
- تكوين Application Insights للمراقبة
- إعداد structured logging
- تكوين health checks للتطبيق
طريقة التفكير:
هنشئ نظام مراقبة وتسجيل شامل:
// إضافة Application Insights
services.AddApplicationInsightsTelemetry(options =>
{
options.ConnectionString = Configuration["ApplicationInsights:ConnectionString"];
});
// تكوين health checks
services.AddHealthChecks()
.AddDbContextCheck<ApplicationDbContext>("database")
.AddRedis(Configuration.GetConnectionString("RedisConnection"), "redis")
.AddCheck<ExternalApiHealthCheck>("external-api");
// وتكوين middleware للـ health check endpoints
app.UseHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
app.UseHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = registration => registration.Tags.Contains("ready"),
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
app.UseHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = _ => false,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
// تنفيذ middleware للـ request logging
app.UseMiddleware<RequestLoggingMiddleware>();
// تنفيذ middleware للـ RequestLoggingMiddleware
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestLoggingMiddleware> _logger;
public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var requestTime = DateTime.UtcNow;
var requestId = Activity.Current?.Id ?? context.TraceIdentifier;
// Log the request
_logger.LogInformation(
"Request {RequestMethod} {RequestPath} started at {RequestTime} with ID {RequestId}",
context.Request.Method,
context.Request.Path,
requestTime,
requestId);
// Enable request body rewind
context.Request.EnableBuffering();
try
{
await _next(context);
var elapsedMs = (DateTime.UtcNow - requestTime).TotalMilliseconds;
_logger.LogInformation(
"Request {RequestMethod} {RequestPath} with ID {RequestId} completed in {ElapsedMilliseconds}ms with status code {StatusCode}",
context.Request.Method,
context.Request.Path,
requestId,
elapsedMs,
context.Response.StatusCode);
}
catch (Exception ex)
{
var elapsedMs = (DateTime.UtcNow - requestTime).TotalMilliseconds;
_logger.LogError(
ex,
"Request {RequestMethod} {RequestPath} with ID {RequestId} failed after {ElapsedMilliseconds}ms",
context.Request.Method,
context.Request.Path,
requestId,
elapsedMs);
throw;
}
}
}
14. اختبار البيئة والتحقق من الإعداد
التنفيذ:
- التأكد من اكتمال جميع الإعدادات
- اختبار البناء والتشغيل محلياً
- التحقق من عمل الـ CI/CD pipeline
- اختبار اتصال قاعدة البيانات والخدمات الخارجية
طريقة التفكير:
هنفذ قائمة تحقق للتأكد من جاهزية البيئة:
1. التحقق من البناء:
dotnet build --configuration Release
2. تشغيل الاختبارات:
dotnet test --configuration Release
3. التحقق من هجرة قاعدة البيانات:
dotnet ef database update
4. تشغيل التطبيق محلياً والتحقق من Swagger:
dotnet run
ثم فتح https://localhost:5001/swagger
5. اختبار Docker compose:
docker-compose up -d
التحقق من تشغيل جميع الخدمات بنجاح
6. التحقق من البنية باستخدام سكريبت التحقق:
public static class EnvironmentValidator
{
public static async Task ValidateAsync(IServiceProvider serviceProvider, ILogger logger)
{
try
{
logger.LogInformation("Starting environment validation...");
// التحقق من اتصال قاعدة البيانات
using (var scope = serviceProvider.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
logger.LogInformation("Validating database connection...");
var canConnect = await dbContext.Database.CanConnectAsync();
if (canConnect)
{
logger.LogInformation("Database connection successful.");
}
else
{
logger.LogError("Failed to connect to the database!");
throw new Exception("Database connection failed");
}
// التحقق من وجود الـ migrations
logger.LogInformation("Checking pending migrations...");
var pendingMigrations = await dbContext.Database.GetPendingMigrationsAsync();
var pendingMigrationsList = pendingMigrations.ToList();
if (pendingMigrationsList.Any())
{
logger.LogWarning("There are {Count} pending migrations: {Migrations}",
pendingMigrationsList.Count,
string.Join(", ", pendingMigrationsList));
}
else
{
logger.LogInformation("No pending migrations found.");
}
}
// التحقق من اتصال Redis
logger.LogInformation("Validating Redis connection...");
var redisCache = serviceProvider.GetRequiredService<IDistributedCache>();
await redisCache.SetStringAsync("test_key", "test_value", new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1)
});
var retrievedValue = await redisCache.GetStringAsync("test_key");
if (retrievedValue == "test_value")
{
logger.LogInformation("Redis connection successful.");
}
else
{
logger.LogError("Failed to connect to Redis!");
throw new Exception("Redis connection failed");
}
// التحقق من توفر خدمات البريد الإلكتروني
logger.LogInformation("Validating email service configuration...");
var emailService = serviceProvider.GetRequiredService<IEmailService>();
// التحقق من خدمة التخزين
logger.LogInformation("Validating storage service configuration...");
var storageService = serviceProvider.GetRequiredService<IImageService>();
// التحقق من تكوين JWT
logger.LogInformation("Validating JWT configuration...");
var jwtSettings = serviceProvider.GetRequiredService<IOptions<JwtSettings>>().Value;
if (string.IsNullOrEmpty(jwtSettings.Secret) || jwtSettings.Secret == "PLACEHOLDER_SECRET_TO_BE_REPLACED")
{
logger.LogError("JWT Secret is not properly configured!");
throw new Exception("JWT configuration is invalid");
}
logger.LogInformation("Environment validation completed successfully.");
}
catch (Exception ex)
{
logger.LogError(ex, "Environment validation failed!");
throw;
}
}
}
// استدعاء التحقق عند بدء التشغيل
var app = builder.Build();
// في بيئة التطوير، نتحقق من صحة الإعداد
if (app.Environment.IsDevelopment())
{
try
{
using var scope = app.Services.CreateScope();
var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
await EnvironmentValidator.ValidateAsync(scope.ServiceProvider, logger);
logger.LogInformation("Development environment setup is valid.");
}
catch (Exception ex)
{
var logger = app.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred during environment validation.");
// في بيئة التطوير، نسمح بالاستمرار حتى مع وجود أخطاء
// ولكن نعرض تحذيراً واضحاً
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("WARNING: Development environment setup has issues!");
Console.WriteLine(ex.Message);
Console.ResetColor();
}
}
15. إعداد Visual Studio Code Settings
التنفيذ:
- إنشاء ملفات settings.json و launch.json و tasks.json
- تكوين الـ extensions الموصى بها
- إعداد الـ debugging configurations
طريقة التفكير:
// .vscode/settings.json
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[csharp]": {
"editor.defaultFormatter": "ms-dotnettools.csharp"
},
"dotnet.defaultSolution": "Hamla.sln",
"cSpell.words": [
"Hamla",
"appsettings",
"signin",
"signup"
],
"files.exclude": {
"**/.git": true,
"**/.vs": true,
"**/bin": true,
"**/obj": true
}
}
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Hamla API",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/Hamla.API/bin/Debug/net6.0/Hamla.API.dll",
"args": [],
"cwd": "${workspaceFolder}/Hamla.API",
"stopAtEntry": false,
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)",
"uriFormat": "%s/swagger"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": "Docker Debug",
"type": "docker",
"request": "launch",
"preLaunchTask": "docker-run: debug",
"netCore": {
"appProject": "${workspaceFolder}/Hamla.API/Hamla.API.csproj"
}
}
]
}
// .vscode/tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Hamla.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/Hamla.API/Hamla.API.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/Hamla.API/Hamla.API.csproj"
],
"problemMatcher": "$msCompile"
},
{
"type": "docker-build",
"label": "docker-build: debug",
"dependsOn": ["build"],
"dockerBuild": {
"tag": "hamla:dev",
"target": "base",
"dockerfile": "${workspaceFolder}/Dockerfile",
"context": "${workspaceFolder}",
"pull": true
},
"netCore": {
"appProject": "${workspaceFolder}/Hamla.API/Hamla.API.csproj"
}
},
{
"type": "docker-run",
"label": "docker-run: debug",
"dependsOn": ["docker-build: debug"],
"dockerRun": {},
"netCore": {
"appProject": "${workspaceFolder}/Hamla.API/Hamla.API.csproj",
"enableDebugging": true
}
}
]
}
// .vscode/extensions.json
{
"recommendations": [
"ms-dotnettools.csharp",
"ms-azuretools.vscode-docker",
"ms-vscode.vscode-node-azure-pack",
"ms-mssql.mssql",
"formulahendry.dotnet-test-explorer",
"jmrog.vscode-nuget-package-manager",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"eamodio.gitlens",
"streetsidesoftware.code-spell-checker",
"redhat.vscode-yaml"
]
}
16. توثيق الإعداد والبدء السريع للمطورين
التنفيذ:
- إنشاء README.md شامل للمشروع
- إعداد وثائق لإرشادات التطوير وأفضل الممارسات
- تضمين تعليمات البدء السريع لأعضاء الفريق الجدد
طريقة التفكير:
# Hamla Travel Platform API
API backend لمنصة Hamla للسفر التي تربط المسافرين بوكالات السفر وتمكن من حجز الرحلات.
## البنية التقنية
- **Backend**: ASP.NET Core 6 مع Entity Framework Core
- **Database**: SQL Server
- **Authentication**: ASP.NET Identity مع JWT
- **Caching**: Redis
- **Storage**: Azure Blob Storage
- **Monitoring**: Application Insights
- **CI/CD**: GitHub Actions / Azure DevOps
## متطلبات التطوير
- [.NET 6 SDK](https://dotnet.microsoft.com/download/dotnet/6.0)
- [Visual Studio 2022](https://visualstudio.microsoft.com/vs/) أو [VS Code](https://code.visualstudio.com/)
- [SQL Server](https://www.microsoft.com/en-us/sql-server/sql-server-downloads) (أو SQL Server Express)
- [Docker Desktop](https://www.docker.com/products/docker-desktop)
- [Git](https://git-scm.com/downloads)
## البدء السريع
### 1. استنساخ المشروع
```bash
git clone https://github.com/hamla-travel/hamla-api.git
cd hamla-api
```\
### 2. إعداد قاعدة البيانات المحلية
- إنشاء قاعدة بيانات SQL Server محلية باسم `Hamla`
- تحديث سلسلة الاتصال في `appsettings.Development.json` أو استخدام User Secrets:
```bash
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=(localdb)\\mssqllocaldb;Database=Hamla;Trusted_Connection=True;MultipleActiveResultSets=true"
```\
### 3. تطبيق هجرات قاعدة البيانات
```bash
dotnet ef database update
```\
### 4. تشغيل التطبيق
```bash
dotnet run --project Hamla.API
```\
أو باستخدام Docker:
```bash
docker-compose up -d
```\
### 5. الوصول إلى واجهة Swagger
افتح المتصفح على الرابط:
- https://localhost:5001/swagger (عند التشغيل باستخدام dotnet run)
- https://localhost:5001/swagger (عند التشغيل باستخدام Docker)
## هيكل المشروع
```\
Hamla/
├── Hamla.API # طبقة واجهة برمجة التطبيقات
├── Hamla.Core # نماذج المجال والمنطق التجاري
├── Hamla.Infrastructure # الوصول للبيانات والخدمات الخارجية
├── Hamla.Shared # DTOs ومساعدات مشتركة
└── Hamla.Tests # اختبارات الوحدة والتكامل
```\
## إرشادات التطوير
### العمل بالفروع
نستخدم استراتيجية Git Flow:
- `main`: فرع الإنتاج (مستقر)
- `develop`: فرع التطوير الرئيسي
- `feature/*`: للميزات الجديدة
- `bugfix/*`: لإصلاحات الأخطاء
- `release/*`: لإصدارات جديدة
عند العمل على ميزة جديدة:
```bash
git checkout develop
git pull
git checkout -b feature/your-feature-name
# العمل على الميزة...
git commit -m "Add your feature"
git push -u origin feature/your-feature-name
# إنشاء طلب سحب (Pull Request) إلى develop
```\
### بناء الاختبارات
نستخدم xUnit للاختبارات. لتشغيل الاختبارات:
```bash
dotnet test
```\
### معايير الكود
- استخدم ال C# Coding Conventions الخاصة بـ Microsoft
- اكتب تعليقات XML لجميع الواجهات العامة (interfaces & public methods)
- استخدم سجل التغييرات (CHANGELOG.md) لتتبع التغييرات
### التعليمات البرمجية
- استخدم Repository Pattern للوصول إلى البيانات
- استخدم CQRS عند الحاجة للعمليات المعقدة
- ضع الـ business logic في طبقة Core
- ضع Service Interfaces في Core والتنفيذ في Infrastructure
## النشر
يتم النشر تلقائيًا عبر CI/CD pipeline:
- الدمج مع `develop` ينشر إلى بيئة التطوير
- الدمج مع `main` ينشر إلى بيئة الإنتاج
## المراقبة والتسجيل
- استخدم Application Insights للمراقبة
- لوحة المراقبة: https://portal.azure.com/#@hamla-travel.com/resource/subscriptions/...
## الموارد المفيدة
- [EF Core Documentation](https://docs.microsoft.com/en-us/ef/core/)
- [ASP.NET Core Documentation](https://docs.microsoft.com/en-us/aspnet/core/)
- [Clean Architecture](https://github.com/jasontaylordev/CleanArchitecture)
```\
## النقاط الرئيسية التي يجب مراع
### الأمان
- لا تقم أبدًا بتخزين الأسرار (مثل مفاتيح API أو كلمات مرور) في التعليمات البرمجية أو في ملفات تكوين مرفوعة على Git
- استخدم User Secrets في بيئة التطوير وEnvironment Variables في بيئة الإنتاج
- تحقق دائمًا من صحة جميع المدخلات من العميل
- استخدم HTTPS في جميع البيئات، حتى في التطوير
### الأداء
- استخدم التخزين المؤقت (Redis) للبيانات التي يتم الوصول إليها بشكل متكرر
- قم بتحسين استعلامات قاعدة البيانات باستخدام Eager Loading المناسب
- استخدم Pagination لقوائم البيانات الكبيرة
- ضع في اعتبارك استخدام Async/Await لجميع العمليات طويلة المدى
### قابلية الصيانة
- التزم بمبادئ SOLID
- اكتب اختبارات لتغطية المنطق الأساسي
- وثّق الفئات والوظائف الرئيسية
- استخدم أنماط التصميم المعروفة والثابتة
## حل المشكلات الشائعة
### مشكلات الاتصال بقاعدة البيانات
1. تأكد من أن خادم SQL Server يعمل
2. تحقق من سلسلة الاتصال في appsettings.json
3. تأكد من تطبيق الهجرات: `dotnet ef database update`
### مشكلات CORS
1. تأكد من إعداد CORS بشكل صحيح في Startup.cs
2. تحقق من أن origins العميل مضافة إلى قائمة السماح
### مشكلات JWT
1. تأكد من تكوين مفتاح JWT السري بشكل صحيح
2. تحقق من صلاحية التوكن ووقت انتهاء الصلاحية
### مشكلات Docker
1. تأكد من تشغيل Docker Desktop
2. امسح الصور والحاويات القديمة: `docker system prune -a`
3. أعد بناء الصور: `docker-compose build --no-cache`
17. إعداد الاتفاقيات والقواعد للفريق
التنفيذ:
- وضع اتفاقيات تسمية للكود
- تحديد معايير كتابة الكود وأفضل الممارسات
- إعداد عملية مراجعة الكود والاعتماد
طريقة التفكير:
# اتفاقيات ومعايير الكود لفريق Hamla
## اتفاقيات التسمية
### العامة
- استخدم PascalCase للفئات والواجهات والخصائص والمساحات: `TripService`, `IUserRepository`
- استخدم camelCase للمتغيرات المحلية والمعلمات: `userId`, `bookingDate`
- استخدم UPPER_CASE للثوابت: `DEFAULT_PAGE_SIZE`
- أضف البادئة "I" للواجهات: `IEmailService`
- أضف اللاحقة "Dto" لكائنات نقل البيانات: `TripDto`
- أضف اللاحقة "Repository" لمستودعات البيانات: `TripRepository`
- أضف اللاحقة "Service" للخدمات: `NotificationService`
- أضف اللاحقة "Controller" لوحدات التحكم: `BookingController`
### قاعدة البيانات
- استخدم المفرد للجداول: `User` وليس `Users`
- استخدم PascalCase للجداول والأعمدة
- استخدم FK كلاحقة للمفاتيح الخارجية: `AgencyId`
### واجهة API
- استخدم kebab-case للمسارات: `/api/booking-details` وليس `/api/bookingDetails`
- استخدم مصطلحات RESTful (GET, POST, PUT, DELETE)
- استخدم أسماء جمع للموارد: `/api/trips` وليس `/api/trip`
## هيكل الكود
### الفئات والملفات
- ملف واحد لكل فئة
- فصل واجهات البرمجة عن التنفيذ
- ترتيب العناصر في الفئة: الحقول، البناء، الخصائص، الأساليب العامة، الأساليب الخاصة
- لا تزيد الفئة عن 500 سطر من التعليمات البرمجية
- لا تزيد الدالة عن 50 سطرًا من التعليمات البرمجية
### التعليقات (تكملة)
- استخدم تعليقات XML لجميع واجهات API العامة
- قم بتوثيق جميع المعلمات والقيم المرجعة
- ضع تعليقات على الشفرة المعقدة أو غير البديهية
- استخدم TODO: للمهام المستقبلية مع رقم القضية إن أمكن
مثال للتعليقات:
```csharp
/// <summary>
/// يقوم بجلب الرحلات المتاحة بناءً على معايير البحث
/// </summary>
/// <param name="filter">معايير الفلترة والبحث</param>
/// <returns>قائمة بالرحلات المطابقة للمعايير</returns>
/// <exception cref="ArgumentException">في حالة تقديم معايير بحث غير صالحة</exception>
public async Task<PagedResult<TripDto>> GetTripsAsync(TripFilterDto filter)
{
// TODO: #123 تنفيذ الفلترة الجغرافية بناءً على المسافة
}
```\
## معايير جودة الكود
### التغطية بالاختبارات
- تغطية 80% على الأقل للمنطق الأساسي
- اختبارات وحدة لكل خدمة ومستودع
- اختبارات تكامل لكل مسار API
### مراجعة الكود
- يجب أن تمر جميع التغييرات بمراجعة الكود قبل الدمج
- يُتطلب على الأقل مراجع واحد لاعتماد طلب السحب
- يجب أن تجتاز جميع الاختبارات الآلية قبل الدمج
### القواعد المطبقة آليًا
- استخدام StyleCop لفرض معايير التنسيق
- استخدام EditorConfig لتوحيد إعدادات المحرر
- فحص نوعية الكود باستخدام SonarQube
## عملية التطوير
### خطوات العمل على ميزة جديدة
1. التأكد من وجود قضية (Issue) في نظام التتبع
2. إنشاء فرع جديد من `develop` باسم `feature/[رقم-القضية]-وصف-موجز`
3. تنفيذ الميزة مع الالتزام بمعايير الكود
4. كتابة الاختبارات اللازمة
5. دفع التغييرات وإنشاء طلب سحب
6. الاستجابة لملاحظات المراجعة
7. الدمج بعد الموافقة
### رسائل Commit
- استخدم الصيغة: `نوع: وصف موجز (القضية #رقم)`
- الأنواع: feat، fix، docs، style، refactor، test، chore
- اجعل الوصف واضحًا وموجزًا (أقل من 50 حرفًا)
- استخدم الوصف التفصيلي عند الحاجة
مثال: `feat: إضافة فلترة متقدمة للرحلات (القضية #42)`
### طلبات السحب (Pull Requests)
- عنوان واضح يصف التغيير
- وصف مفصل للتغييرات
- ربط القضية ذات الصلة
- قائمة تحقق للمراجع
## الأدوات والإعدادات
### ملف EditorConfig
```\
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{json,yml,yaml}]
indent_size = 2
[*.md]
trim_trailing_whitespace = false
```\
### ملف .gitattributes (تكملة)
```\
* text=auto
*.cs text diff=csharp
*.sln text eol=crlf
*.csproj text eol=crlf
*.md text
*.json text
*.sql text
*.yml text
*.yaml text
*.config text
*.sh text eol=lf
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.pdf binary
```\
### ملف StyleCop.json
```json
{
"$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
"settings": {
"documentationRules": {
"companyName": "Hamla Travel",
"copyrightText": "Copyright (c) {companyName}. All rights reserved.",
"xmlHeader": false,
"fileNamingConvention": "stylecop"
},
"orderingRules": {
"usingDirectivesPlacement": "outsideNamespace",
"systemUsingDirectivesFirst": true
},
"namingRules": {
"allowedHungarianPrefixes": []
}
}
}
```\
## الأمن والخصوصية
### قواعد عامة
- لا تقم بتخزين أي بيانات حساسة في التعليمات البرمجية
- استخدم البيئات الآمنة لمشاركة الأسرار
- قم بتشفير جميع البيانات الحساسة قبل التخزين
- تحقق من جميع مدخلات المستخدم
### معالجة البيانات
- تعامل مع جميع البيانات الشخصية وفقًا لسياسة الخصوصية
- قم بتنفيذ آليات الحذف للامتثال لـ "الحق في النسيان"
- قم بتسجيل الوصول إلى البيانات الحساسة
### اختبارات الأمان
- قم بإجراء فحص أمني دوري للكود
- تنفيذ اختبارات أمنية مثل اختبار الاختراق
- استخدم أدوات تحليل أمان التعليمات البرمجية (SAST)
## إرشادات خاصة بالتطبيق
### أفضل الممارسات للمشروع
- استخدم نهج Repository للوصول إلى البيانات
- نفذ نمط CQRS للعمليات المعقدة
- استخدم MediatR للتعامل مع الطلبات داخل الخدمات
- نفذ Fluent Validation لجميع DTOs
### نمط التعليمات البرمجية
- اتبع مبادئ SOLID
- استخدم Dependency Injection لكل المكونات
- تجنب "السحر" في الكود - اجعل الشفرة واضحة وبديهية
- اكتب الكود للقراءة البشرية، وليس فقط للآلات
### نصائح لتحسين الأداء
- استخدم الـ async/await بشكل صحيح
- استخدم التخزين المؤقت للبيانات المستقرة
- تجنب N+1 problem في استعلامات قاعدة البيانات
- استخدم Compiled Queries للعمليات المتكررة
18. التدريب وتجهيز الفريق
التنفيذ:
- إعداد جلسة تدريبية للفريق حول بيئة التطوير
- توفير وثائق ومصادر تعليمية إضافية
- تعيين موجهين للأعضاء الجدد
طريقة التفكير:
# خطة التدريب والتأهيل لفريق Hamla
## جلسة الإعداد الأولية
### الجدول الزمني
- **اليوم الأول**: إعداد البيئة والمقدمة
- **اليوم الثاني**: معمارية التطبيق والمكونات الرئيسية
- **اليوم الثالث**: سير العمل وأفضل الممارسات
### محتوى اليوم الأول
1. **مقدمة المشروع** (60 دقيقة)
- نظرة عامة على Hamla
- الأهداف وخارطة الطريق
- نطاق المشروع ومتطلباته
2. **إعداد بيئة التطوير** (90 دقيقة)
- تثبيت الأدوات المطلوبة
- استنساخ المستودع وإعداده
- تشغيل المشروع محلياً لأول مرة
3. **استعراض الهيكل والاتفاقيات** (60 دقيقة)
- هيكل المشروع
- اتفاقيات التسمية والترميز
- معايير الجودة والمراجعة
### محتوى اليوم الثاني
1. **المعمارية الهندسية** (90 دقيقة)
- شرح Clean Architecture
- فصل المسؤوليات
- تدفق البيانات داخل التطبيق
2. **العناصر الرئيسية** (120 دقيقة)
- نظام المستخدمين والمصادقة
- نظام الرحلات والحجوزات
- نظام التقييمات والإشعارات
3. **قاعدة البيانات والـ ORM** (60 دقيقة)
- مخطط قاعدة البيانات
- استخدام Entity Framework Core
- عمليات الترحيل والتحديث
### محتوى اليوم الثالث
1. **سير العمل اليومي** (60 دقيقة)
- العمل بالفروع وإدارة المستودع
- نظام تتبع المشكلات وإدارة المهام
- عملية مراجعة الكود
2. **أفضل الممارسات** (90 دقيقة)
- كتابة الاختبارات
- التعامل مع الأداء والأمان
- توثيق الكود والواجهات
3. **ورشة عمل عملية** (120 دقيقة)
- تنفيذ ميزة بسيطة
- كتابة اختبارات الوحدة
- تقديم طلب سحب ومراجعته
## الموارد والوثائق
### الموارد الداخلية
- مستندات التصميم والمتطلبات
- وثائق API الداخلية
- أمثلة ونماذج الكود
### الموارد الخارجية
- [Microsoft ASP.NET Core Documentation](https://docs.microsoft.com/en-us/aspnet/core/)
- [Entity Framework Core Documentation](https://docs.microsoft.com/en-us/ef/core/)
- [Clean Architecture by Jason Taylor](https://github.com/jasontaylordev/CleanArchitecture)
- [RESTful API Design Best Practices](https://restfulapi.net/)
## نظام التوجيه
### الهيكل التوجيهي
- كل عضو جديد سيتم تعيين موجه له من أعضاء الفريق ذوي الخبرة
- جلسات توجيه أسبوعية (ساعة واحدة)
- مراجعة الكود المستمرة والتغذية الراجعة
### مسؤوليات الموجه
- توجيه الأعضاء الجدد خلال أول 3 أشهر
- استعراض الكود والتغذية الراجعة البناءة
- المساعدة في حل المشكلات التقنية
- نقل المعرفة حول الممارسات الجيدة
### أدوات الدعم المستمر
- قناة Slack مخصصة للدعم التقني
- اجتماعات "Ask Me Anything" أسبوعية
- جلسات تبادل المعرفة الشهرية
ملخص إعداد بيئة التطوير 📋
بعد إكمال جميع هذه الخطوات، تكون قد أسست بيئة تطوير احترافية ومتكاملة لمشروع Hamla تتضمن:
-
اختيار حزمة تكنولوجيا مناسبة تتماشى مع متطلبات المشروع وتضمن الأداء والمرونة والقابلية للتوسع.
-
هيكل مشروع منظم يتبع نمط Clean Architecture لضمان الفصل الواضح بين طبقات التطبيق.
-
إعداد متكامل لقاعدة البيانات مع Entity Framework Core وباستخدام Identity Package لإدارة المستخدمين.
-
نظام أمان قوي يعتمد على JWT والتشفير والممارسات الأمنية الأخرى.
-
أدوات تطوير متطورة مثل Docker وCI/CD وSwagger وLogging.
-
معايير واتفاقيات موثقة للفريق لضمان اتساق الكود وسهولة صيانته.
هذه البيئة ستسمح لفريق التطوير بالعمل بكفاءة على المشروع، وتقليل الأخطاء، وزيادة سرعة التطوير، مع الحفاظ على جودة الكود والتوثيق المناسب.
Database Schema Using Mermaid (Including Admin Role)
erDiagram ApplicationUser { int id PK string userName string email string passwordHash string phoneNumber bool emailConfirmed bool phoneNumberConfirmed string userType DateTime createdAt DateTime lastLoginAt } UserProfile { int id PK int userId FK string firstName string lastName string profilePicture string city string country string preferredLanguage int loyaltyPoints DateTime birthDate } AgencyProfile { int id PK int userId FK string companyName string logo string description string websiteUrl string contactEmail string contactPhone bool isVerified DateTime foundedDate float averageRating } AdminProfile { int id PK int userId FK string adminRole int permissionLevel DateTime lastActivityAt } Trip { int id PK int agencyId FK string title string destination DateTime startDate DateTime endDate decimal price string description int maxTravelers int availableSpots string status DateTime createdAt DateTime updatedAt } TripImage { int id PK int tripId FK string imageUrl bool isMain int displayOrder } Booking { int id PK int userId FK int tripId FK int agencyId FK string status int travelersCount decimal totalPrice bool isPaid string paymentMethod string bookingReference DateTime createdAt DateTime updatedAt } BookingTraveler { int id PK int bookingId FK string fullName string documentNumber DateTime birthDate string gender string nationality } Review { int id PK int userId FK int agencyId FK int bookingId FK int rating string comment bool isVerified DateTime createdAt } Notification { int id PK int recipientId FK string type string title string content bool isRead string link DateTime createdAt } IdentityRole { int id PK string name string normalizedName } UserRole { int userId FK int roleId FK } RefreshToken { int id PK int userId FK string token DateTime expiryDate bool isRevoked } ApplicationUser ||--o| UserProfile : "has" ApplicationUser ||--o| AgencyProfile : "has" ApplicationUser ||--o| AdminProfile : "has" ApplicationUser }o--o{ IdentityRole : "has roles" ApplicationUser ||--o{ RefreshToken : "has tokens" ApplicationUser ||--o{ Booking : "makes" ApplicationUser ||--o{ Review : "writes" ApplicationUser ||--o{ Notification : "receives" IdentityRole }o--o{ UserRole : "assigned to" UserRole }o--o{ ApplicationUser : "belongs to" AgencyProfile ||--o{ Trip : "offers" Trip ||--o{ TripImage : "has" Trip ||--o{ Booking : "has" Booking ||--o{ BookingTraveler : "includes" Booking ||--o| Review : "may have"
تفكيري في استخدام Identity Package من Microsoft
عشان هنستخدم الـ Identity Package اللي في EF Core من Microsoft، فده هيأثر على تصميم الـ Database بالشكل ده:
-
الـ Built-in Tables:
- AspNetUsers: هتكون قاعدة لكل أنواع الـ users (مسافرين، وكالات، أدمن)
- AspNetRoles: هتخزن الأدوار (Traveler، Agency، Admin)
- AspNetUserRoles: هتربط بين الـ users والـ roles
- AspNetUserClaims: هتسمح بتخزين معلومات إضافية عن الـ users
- AspNetUserLogins: هتدعم الـ social logins
-
طريقة التنظيم:
- استخدمت تصميم بيفصل بين جدول الـ Identity الأساسي (ApplicationUser) والـ profile data
- كل نوع من الـ users (مسافر، وكالة، أدمن) ليه جدول منفصل للـ profile details
- ده هيسمح بالمرونة في إضافة حقول خاصة بكل نوع
-
ميزات استخدام الـ Identity Package:
- هنستفيد من كل ميزات الـ Identity الجاهزة زي:
- إدارة الـ users والـ roles
- تشفير الـ passwords
- two-factor authentication
- account lockout
- email confirmation
- external logins (Google, Facebook)
- هنستخدم الـ UserManager وRoleManager APIs اللي بتسهل العمليات دي
- هنستفيد من كل ميزات الـ Identity الجاهزة زي:
-
التعديلات المطلوبة:
- هنعمل extend للـ IdentityUser class عشان نضيف حقول زي userType
- هنعمل ApplicationUser ترث من IdentityUser
- هنستخدم الـ navigation properties للربط بين الـ ApplicationUser والـ profile tables
ملاحظات مهمة على التصميم:
-
User Management:
- كل المستخدمين (مسافرين، وكالات، أدمن) هيكونوا في جدول واحد لكن بـ roles مختلفة
- فيه حقل userType يساعد في التمييز بينهم سريعاً
-
Trip Management:
- فصلت الـ Trip Images في جدول منفصل لدعم multiple images
- ضفت statuses للـ trips عشان نقدر نتحكم في ظهورها (نشطة، منتهية، ملغاة)
-
Booking System:
- بيسمح بتسجيل تفاصيل المسافرين المختلفين في نفس الـ booking
- بيتتبع حالة الحجز والدفع
-
Review System:
- ربطت الـ reviews بالـ bookings عشان نضمن إن الـ reviews بتكون من مستخدمين فعلاً استخدموا الخدمة
- ضفت حقل isVerified عشان الأدمن يقدر يتحقق من الـ reviews
-
Notification System:
- صممته بشكل عام يسمح بإرسال إشعارات لأي نوع من المستخدمين
- بيدعم الروابط للانتقال لصفحات محددة
هذا التصميم هيكون متوافق مع الـ Identity Package وفي نفس الوقت هيلبي احتياجات المشروع المتنوعة.
بعد ما اخترنا الـ Tech Stack وعملنا تصميم الـ Database هنبدأ مرحلة الـ Development. هنفصل الخطوات اللي المفروض نعملها بالترتيب:
Step 5: Project Structure and Basic Setup (2-3 Days) 📂
1. إعداد Solution Structure
التنفيذ:
- إنشاء solution جديد باستخدام Visual Studio
- تنظيم المشروع بنمط الـ Clean Architecture
- إعداد الـ layers المختلفة للمشروع
طريقة التفكير:
هنظم المشروع بطريقة الـ Clean Architecture عشان:
- يبقى سهل صيانته
- يسمح بـ separation of concerns
- يسهل عمل unit testing
Solution structure:
- Hamla.API - الـ presentation layer (Web API controllers)
- Hamla.Core - الـ domain models و business logic
- Hamla.Infrastructure - الـ data access والـ external services
- Hamla.Shared - الـ DTOs والـ utilities المشتركة
- Hamla.Tests - الـ unit tests والـ integration tests
2. إعداد Entity Framework Core مع Identity
التنفيذ:
- تثبيت الـ NuGet packages اللازمة
- إعداد ApplicationDbContext
- تكوين الـ Identity models
- عمل Migrations للـ database
طريقة التفكير:
هبدأ بإعداد الـ EF Core مع الـ Identity:
1. تثبيت الـ packages:
- Microsoft.AspNetCore.Identity.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.SqlServer (أو PostgreSQL حسب الاختيار)
- Microsoft.EntityFrameworkCore.Tools
2. إنشاء ApplicationUser class:
public class ApplicationUser : IdentityUser
{
public string UserType { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime LastLoginAt { get; set; }
}
3. إنشاء ApplicationDbContext:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{ }
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<AgencyProfile> AgencyProfiles { get; set; }
// ...باقي الـ DbSets
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// إضافة الـ configurations
}
}
4. عمل Migration أولي:
- Add-Migration InitialCreate
- Update-Database
3. إعداد الـ Dependency Injection
التنفيذ:
- إعداد الـ services في Startup.cs
- تكوين الـ repositories والـ services
- إعداد الـ middleware pipeline
طريقة التفكير:
هعمل setup للـ DI بطريقة منظمة:
// في Startup.cs أو Program.cs (حسب الإصدار)
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// إعداد الـ repositories
services.AddScoped<ITripRepository, TripRepository>();
services.AddScoped<IBookingRepository, BookingRepository>();
// ...باقي الـ repositories
// إعداد الـ services
services.AddScoped<IAuthService, AuthService>();
services.AddScoped<ITripService, TripService>();
// ...باقي الـ services
// إعداد الـ JWT Authentication
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
// JWT configuration
});
Step 6: Authentication and User Management (5-7 Days) 👤
1. تنفيذ Authentication Services
التنفيذ:
- إنشاء IAuthService وتنفيذه
- عمل JWT generation logic
- تنفيذ خدمات التسجيل وتسجيل الدخول
طريقة التفكير:
بفكر في الـ authentication flow:
1. إنشاء interface للـ AuthService:
public interface IAuthService
{
Task<AuthResult> RegisterUserAsync(RegisterDto model);
Task<AuthResult> RegisterAgencyAsync(RegisterAgencyDto model);
Task<AuthResult> LoginAsync(LoginDto model);
Task<AuthResult> RefreshTokenAsync(RefreshTokenDto model);
Task<bool> LogoutAsync(string userId);
}
2. تنفيذ الـ JWT generation:
private string GenerateJwtToken(ApplicationUser user, IList<string> roles)
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.Name, user.UserName)
};
// إضافة الـ roles كـ claims
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
// إنشاء الـ token
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _configuration["JWT:Issuer"],
audience: _configuration["JWT:Audience"],
claims: claims,
expires: DateTime.Now.AddHours(1),
signingCredentials: creds
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
2. تنفيذ User Profile Management
التنفيذ:
- إنشاء الـ DTOs للـ profiles
- تنفيذ الـ services لإدارة الـ profiles
- تنفيذ الـ controllers للـ profile management
طريقة التفكير:
هقسم الـ profile management لوظائف مختلفة:
1. الـ DTOs:
public class UserProfileDto
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public string Country { get; set; }
// ...باقي الحقول
}
2. Service للـ user profiles:
public interface IUserProfileService
{
Task<UserProfileDto> GetProfileAsync(string userId);
Task<Result> UpdateProfileAsync(string userId, UserProfileDto model);
Task<Result> UploadProfilePictureAsync(string userId, IFormFile file);
}
3. تنفيذ الـ controller:
[Authorize]
[ApiController]
[Route("api/profiles")]
public class ProfileController : ControllerBase
{
private readonly IUserProfileService _profileService;
// Constructor & DI
[HttpGet]
public async Task<IActionResult> GetProfile()
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var profile = await _profileService.GetProfileAsync(userId);
return Ok(profile);
}
[HttpPut]
public async Task<IActionResult> UpdateProfile(UserProfileDto model)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var result = await _profileService.UpdateProfileAsync(userId, model);
if (result.IsSuccess)
return Ok();
return BadRequest(result.Error);
}
}
3. تنفيذ Role Management والـ Authorization
التنفيذ:
- إعداد الـ roles الأساسية (Traveler, Agency, Admin)
- تنفيذ authorization policies
- تنفيذ role assignment logic
طريقة التفكير:
هعمل Role-based authorization نظام:
1. تعريف الـ roles الأساسية:
public static class ApplicationRoles
{
public const string Admin = "Admin";
public const string Agency = "Agency";
public const string Traveler = "Traveler";
}
2. إعداد الـ policy-based authorization:
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdminRole", policy =>
policy.RequireRole(ApplicationRoles.Admin));
options.AddPolicy("RequireAgencyRole", policy =>
policy.RequireRole(ApplicationRoles.Agency));
options.AddPolicy("RequireTravelerRole", policy =>
policy.RequireRole(ApplicationRoles.Traveler));
});
3. استخدام السياسات في الـ controllers:
[Authorize(Policy = "RequireAgencyRole")]
[HttpPost("trips")]
public async Task<IActionResult> CreateTrip(CreateTripDto model)
{
// التنفيذ
}
Step 7: Trip Management System (4-6 Days) 🏝️
1. تنفيذ Trip CRUD Operations
التنفيذ:
- إنشاء الـ DTOs للـ trips
- تنفيذ الـ repository والـ service
- تنفيذ الـ Trip Controller
طريقة التفكير:
هصمم نظام إدارة الرحلات:
1. الـ DTOs:
public class TripDto
{
public int Id { get; set; }
public string Title { get; set; }
public string Destination { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public decimal Price { get; set; }
// ...باقي الحقول
}
public class CreateTripDto
{
[Required]
public string Title { get; set; }
[Required]
public string Destination { get; set; }
[Required]
public DateTime StartDate { get; set; }
// ...باقي الحقول مع validation
}
2. الـ Trip Service:
public interface ITripService
{
Task<IEnumerable<TripDto>> GetAllTripsAsync(TripFilterDto filter);
Task<TripDto> GetTripByIdAsync(int id);
Task<Result<int>> CreateTripAsync(string agencyId, CreateTripDto model);
Task<Result> UpdateTripAsync(int id, string agencyId, UpdateTripDto model);
Task<Result> DeleteTripAsync(int id, string agencyId);
}
3. Trip Controller:
[ApiController]
[Route("api/trips")]
public class TripController : ControllerBase
{
private readonly ITripService _tripService;
// Constructor & DI
[HttpGet]
public async Task<IActionResult> GetTrips([FromQuery] TripFilterDto filter)
{
var trips = await _tripService.GetAllTripsAsync(filter);
return Ok(trips);
}
[HttpGet("{id}")]
public async Task<IActionResult> GetTrip(int id)
{
var trip = await _tripService.GetTripByIdAsync(id);
if (trip == null)
return NotFound();
return Ok(trip);
}
[Authorize(Policy = "RequireAgencyRole")]
[HttpPost]
public async Task<IActionResult> CreateTrip(CreateTripDto model)
{
var agencyId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var result = await _tripService.CreateTripAsync(agencyId, model);
if (result.IsSuccess)
return CreatedAtAction(nameof(GetTrip), new { id = result.Data }, null);
return BadRequest(result.Error);
}
// باقي الـ endpoints
}
2. تنفيذ Trip Search and Filtering
التنفيذ:
- إنشاء نظام متقدم للبحث والفلترة
- تنفيذ خدمة للبحث بالتاريخ والوجهة والسعر
- تحسين أداء الاستعلامات
طريقة التفكير:
هصمم نظام بحث متقدم:
1. تعريف الـ filter model:
public class TripFilterDto
{
public string Destination { get; set; }
public DateTime? StartDateFrom { get; set; }
public DateTime? StartDateTo { get; set; }
public decimal? MinPrice { get; set; }
public decimal? MaxPrice { get; set; }
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 10;
public string SortBy { get; set; } = "StartDate";
public bool SortAscending { get; set; } = true;
}
2. تنفيذ الـ search في الـ repository:
public async Task<PagedResult<Trip>> GetTripsAsync(TripFilterDto filter)
{
var query = _context.Trips
.Include(t => t.Agency)
.Include(t => t.TripImages)
.AsQueryable();
// تطبيق الفلاتر
if (!string.IsNullOrEmpty(filter.Destination))
{
query = query.Where(t => t.Destination.Contains(filter.Destination));
}
if (filter.StartDateFrom.HasValue)
{
query = query.Where(t => t.StartDate >= filter.StartDateFrom.Value);
}
if (filter.StartDateTo.HasValue)
{
query = query.Where(t => t.StartDate <= filter.StartDateTo.Value);
}
if (filter.MinPrice.HasValue)
{
query = query.Where(t => t.Price >= filter.MinPrice.Value);
}
if (filter.MaxPrice.HasValue)
{
query = query.Where(t => t.Price <= filter.MaxPrice.Value);
}
// تطبيق الترتيب
query = ApplySorting(query, filter.SortBy, filter.SortAscending);
// التصفح (Pagination)
var totalCount = await query.CountAsync();
var items = await query
.Skip((filter.PageNumber - 1) * filter.PageSize)
.Take(filter.PageSize)
.ToListAsync();
return new PagedResult<Trip>
{
Items = items,
TotalCount = totalCount,
PageNumber = filter.PageNumber,
PageSize = filter.PageSize
};
}
3. Image Upload and Management
التنفيذ:
- تنفيذ خدمة رفع الصور للرحلات
- تنفيذ تخزين الصور (local or cloud)
- تنفيذ تحسين وتحجيم الصور
طريقة التفكير:
هصمم نظام لإدارة صور الرحلات:
1. إنشاء الـ interface:
public interface IImageService
{
Task<string> UploadImageAsync(IFormFile file, string folder);
Task<bool> DeleteImageAsync(string imageUrl);
Task<List<string>> UploadMultipleImagesAsync(IFormFileCollection files, string folder);
}
2. تنفيذ الـ service باستخدام cloud storage:
public class AzureBlobImageService : IImageService
{
private readonly BlobServiceClient _blobServiceClient;
private readonly string _containerName;
// Constructor
public async Task<string> UploadImageAsync(IFormFile file, string folder)
{
// تحقق من نوع الملف
if (!IsImageFile(file))
throw new ArgumentException("File must be an image");
// إنشاء اسم فريد للملف
var fileName = $"{Guid.NewGuid()}_{Path.GetFileName(file.FileName)}";
var blobPath = $"{folder}/{fileName}";
// Get container and create if not exists
var containerClient = _blobServiceClient.GetBlobContainerClient(_containerName);
await containerClient.CreateIfNotExistsAsync();
// Upload the file
var blobClient = containerClient.GetBlobClient(blobPath);
using var stream = file.OpenReadStream();
await blobClient.UploadAsync(stream, true);
return blobClient.Uri.ToString();
}
// باقي التنفيذ
}
3. إضافة Endpoint لرفع الصور:
[Authorize(Policy = "RequireAgencyRole")]
[HttpPost("{tripId}/images")]
public async Task<IActionResult> UploadTripImages(int tripId, [FromForm] IFormFileCollection images)
{
var agencyId = User.FindFirstValue(ClaimTypes.NameIdentifier);
// التحقق من ملكية الرحلة
var canEdit = await _tripService.CanEditTripAsync(tripI
[Authorize(Policy = "RequireAgencyRole")]
[HttpPost("{tripId}/images")]
public async Task<IActionResult> UploadTripImages(int tripId, [FromForm] IFormFileCollection images)
{
var agencyId = User.FindFirstValue(ClaimTypes.NameIdentifier);
// التحقق من ملكية الرحلة
var canEdit = await _tripService.CanEditTripAsync(tripId, agencyId);
if (!canEdit)
return Forbid();
// التحقق من عدد الصور
if (images.Count > 10)
return BadRequest("Maximum 10 images allowed per trip");
// رفع الصور
var imageUrls = await _imageService.UploadMultipleImagesAsync(images, "trip-images");
// حفظ روابط الصور في الداتابيز
var result = await _tripService.AddTripImagesAsync(tripId, imageUrls);
if (result.IsSuccess)
return Ok(imageUrls);
return BadRequest(result.Error);
}
Step 8: Booking System (5-7 Days) 🎫
1. تنفيذ Booking Creation and Management
التنفيذ:
- إنشاء الـ DTOs للـ bookings
- تنفيذ الـ repository والـ service
- تنفيذ الـ Booking Controller
طريقة التفكير:
هصمم نظام الحجوزات بشكل متكامل:
1. الـ DTOs:
public class CreateBookingDto
{
[Required]
public int TripId { get; set; }
[Required]
[Range(1, 20)]
public int TravelersCount { get; set; }
public List<BookingTravelerDto> Travelers { get; set; }
public string SpecialRequests { get; set; }
}
public class BookingTravelerDto
{
[Required]
public string FullName { get; set; }
public string DocumentNumber { get; set; }
public DateTime? BirthDate { get; set; }
public string Gender { get; set; }
public string Nationality { get; set; }
}
public class BookingDto
{
public int Id { get; set; }
public int TripId { get; set; }
public string TripTitle { get; set; }
public string Destination { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public int TravelersCount { get; set; }
public decimal TotalPrice { get; set; }
public string Status { get; set; }
public bool IsPaid { get; set; }
public DateTime CreatedAt { get; set; }
public List<BookingTravelerDto> Travelers { get; set; }
}
2. تنفيذ Booking Service
التنفيذ:
public interface IBookingService
{
Task<PagedResult<BookingDto>> GetUserBookingsAsync(string userId, int pageNumber, int pageSize);
Task<PagedResult<BookingDto>> GetAgencyBookingsAsync(string agencyId, string status, int pageNumber, int pageSize);
Task<BookingDto> GetBookingByIdAsync(int id, string userId);
Task<Result<int>> CreateBookingAsync(string userId, CreateBookingDto model);
Task<Result> CancelBookingAsync(int bookingId, string userId);
Task<Result> UpdateBookingStatusAsync(int bookingId, string agencyId, string status);
}
public class BookingService : IBookingService
{
private readonly IBookingRepository _bookingRepository;
private readonly ITripRepository _tripRepository;
private readonly INotificationService _notificationService;
// Constructor with DI
public async Task<Result<int>> CreateBookingAsync(string userId, CreateBookingDto model)
{
// التحقق من وجود الرحلة
var trip = await _tripRepository.GetByIdAsync(model.TripId);
if (trip == null)
return Result.Failure<int>("Trip not found");
// التحقق من توفر المقاعد
if (trip.AvailableSpots < model.TravelersCount)
return Result.Failure<int>($"Only {trip.AvailableSpots} spots available");
// حساب السعر الإجمالي
var totalPrice = trip.Price * model.TravelersCount;
// إنشاء كائن الحجز
var booking = new Booking
{
UserId = userId,
TripId = model.TripId,
AgencyId = trip.AgencyId,
Status = "Pending",
TravelersCount = model.TravelersCount,
TotalPrice = totalPrice,
IsPaid = false,
CreatedAt = DateTime.UtcNow
};
// إضافة تفاصيل المسافرين
if (model.Travelers != null && model.Travelers.Any())
{
booking.BookingTravelers = model.Travelers.Select(t => new BookingTraveler
{
FullName = t.FullName,
DocumentNumber = t.DocumentNumber,
BirthDate = t.BirthDate,
Gender = t.Gender,
Nationality = t.Nationality
}).ToList();
}
// حفظ الحجز في قاعدة البيانات
await _bookingRepository.AddAsync(booking);
// تحديث عدد المقاعد المتاحة في الرحلة
trip.AvailableSpots -= model.TravelersCount;
await _tripRepository.UpdateAsync(trip);
// إرسال إشعارات
await _notificationService.SendBookingNotificationAsync(booking);
return Result.Success(booking.Id);
}
// باقي التنفيذات
}
3. تنفيذ Booking Controller
التنفيذ:
[ApiController]
[Route("api/bookings")]
public class BookingController : ControllerBase
{
private readonly IBookingService _bookingService;
public BookingController(IBookingService bookingService)
{
_bookingService = bookingService;
}
[Authorize(Policy = "RequireTravelerRole")]
[HttpPost]
public async Task<IActionResult> CreateBooking(CreateBookingDto model)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var result = await _bookingService.CreateBookingAsync(userId, model);
if (result.IsSuccess)
return CreatedAtAction(nameof(GetBooking), new { id = result.Data }, null);
return BadRequest(result.Error);
}
[Authorize]
[HttpGet("{id}")]
public async Task<IActionResult> GetBooking(int id)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var booking = await _bookingService.GetBookingByIdAsync(id, userId);
if (booking == null)
return NotFound();
return Ok(booking);
}
[Authorize(Policy = "RequireTravelerRole")]
[HttpGet("my-bookings")]
public async Task<IActionResult> GetMyBookings([FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 10)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var bookings = await _bookingService.GetUserBookingsAsync(userId, pageNumber, pageSize);
return Ok(bookings);
}
[Authorize(Policy = "RequireAgencyRole")]
[HttpGet("agency-bookings")]
public async Task<IActionResult> GetAgencyBookings(
[FromQuery] string status = null,
[FromQuery] int pageNumber = 1,
[FromQuery] int pageSize = 10)
{
var agencyId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var bookings = await _bookingService.GetAgencyBookingsAsync(agencyId, status, pageNumber, pageSize);
return Ok(bookings);
}
[Authorize(Policy = "RequireTravelerRole")]
[HttpPost("{id}/cancel")]
public async Task<IActionResult> CancelBooking(int id)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var result = await _bookingService.CancelBookingAsync(id, userId);
if (result.IsSuccess)
return Ok();
return BadRequest(result.Error);
}
[Authorize(Policy = "RequireAgencyRole")]
[HttpPut("{id}/status")]
public async Task<IActionResult> UpdateBookingStatus(int id, [FromBody] UpdateBookingStatusDto model)
{
var agencyId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var result = await _bookingService.UpdateBookingStatusAsync(id, agencyId, model.Status);
if (result.IsSuccess)
return Ok();
return BadRequest(result.Error);
}
}
Step 9: Review and Rating System (3-5 Days) ⭐
1. تنفيذ Review CRUD Operations
التنفيذ:
- إنشاء الـ DTOs للـ reviews
- تنفيذ الـ repository والـ service
- تنفيذ الـ Review Controller
طريقة التفكير:
public class CreateReviewDto
{
[Required]
public int AgencyId { get; set; }
[Required]
public int BookingId { get; set; }
[Required]
[Range(1, 5)]
public int Rating { get; set; }
[MaxLength(500)]
public string Comment { get; set; }
}
public class ReviewDto
{
public int Id { get; set; }
public int AgencyId { get; set; }
public string AgencyName { get; set; }
public string UserName { get; set; }
public int Rating { get; set; }
public string Comment { get; set; }
public DateTime CreatedAt { get; set; }
}
public interface IReviewService
{
Task<PagedResult<ReviewDto>> GetAgencyReviewsAsync(int agencyId, int pageNumber, int pageSize);
Task<PagedResult<ReviewDto>> GetUserReviewsAsync(string userId, int pageNumber, int pageSize);
Task<Result<int>> CreateReviewAsync(string userId, CreateReviewDto model);
Task<Result> UpdateReviewAsync(int reviewId, string userId, UpdateReviewDto model);
Task<Result> DeleteReviewAsync(int reviewId, string userId);
}
2. تنفيذ Review Service
التنفيذ:
public class ReviewService : IReviewService
{
private readonly IReviewRepository _reviewRepository;
private readonly IBookingRepository _bookingRepository;
private readonly IAgencyProfileRepository _agencyRepository;
public async Task<Result<int>> CreateReviewAsync(string userId, CreateReviewDto model)
{
// التحقق من وجود الحجز وأنه للمستخدم الحالي
var booking = await _bookingRepository.GetByIdAsync(model.BookingId);
if (booking == null || booking.UserId != userId)
return Result.Failure<int>("Invalid booking");
// التحقق من أن الحجز مكتمل
if (booking.Status != "Completed")
return Result.Failure<int>("Can only review completed bookings");
// التحقق من عدم وجود تقييم سابق لنفس الحجز
var existingReview = await _reviewRepository.GetByBookingIdAsync(model.BookingId);
if (existingReview != null)
return Result.Failure<int>("You have already reviewed this booking");
// إنشاء التقييم
var review = new Review
{
UserId = userId,
AgencyId = model.AgencyId,
BookingId = model.BookingId,
Rating = model.Rating,
Comment = model.Comment,
IsVerified = false,
CreatedAt = DateTime.UtcNow
};
await _reviewRepository.AddAsync(review);
// تحديث متوسط تقييم الوكالة
await UpdateAgencyAverageRatingAsync(model.AgencyId);
return Result.Success(review.Id);
}
private async Task UpdateAgencyAverageRatingAsync(int agencyId)
{
var allReviews = await _reviewRepository.GetAllByAgencyIdAsync(agencyId);
if (allReviews.Any())
{
var averageRating = allReviews.Average(r => r.Rating);
await _agencyRepository.UpdateRatingAsync(agencyId, averageRating);
}
}
// باقي التنفيذات
}
3. تنفيذ Review Controller
التنفيذ:
[ApiController]
[Route("api/reviews")]
public class ReviewController : ControllerBase
{
private readonly IReviewService _reviewService;
public ReviewController(IReviewService reviewService)
{
_reviewService = reviewService;
}
[HttpGet("agency/{agencyId}")]
public async Task<IActionResult> GetAgencyReviews(
int agencyId,
[FromQuery] int pageNumber = 1,
[FromQuery] int pageSize = 10)
{
var reviews = await _reviewService.GetAgencyReviewsAsync(agencyId, pageNumber, pageSize);
return Ok(reviews);
}
[Authorize(Policy = "RequireTravelerRole")]
[HttpPost]
public async Task<IActionResult> CreateReview(CreateReviewDto model)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var result = await _reviewService.CreateReviewAsync(userId, model);
if (result.IsSuccess)
return CreatedAtAction(nameof(GetReview), new { id = result.Data }, null);
return BadRequest(result.Error);
}
[HttpGet("{id}")]
public async Task<IActionResult> GetReview(int id)
{
var review = await _reviewService.GetReviewByIdAsync(id);
if (review == null)
return NotFound();
return Ok(review);
}
[Authorize(Policy = "RequireTravelerRole")]
[HttpGet("my-reviews")]
public async Task<IActionResult> GetMyReviews(
[FromQuery] int pageNumber = 1,
[FromQuery] int pageSize = 10)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var reviews = await _reviewService.GetUserReviewsAsync(userId, pageNumber, pageSize);
return Ok(reviews);
}
[Authorize(Policy = "RequireTravelerRole")]
[HttpPut("{id}")]
public async Task<IActionResult> UpdateReview(int id, UpdateReviewDto model)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var result = await _reviewService.UpdateReviewAsync(id, userId, model);
if (result.IsSuccess)
return Ok();
return BadRequest(result.Error);
}
[Authorize(Policy = "RequireTravelerRole")]
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteReview(int id)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var result = await _reviewService.DeleteReviewAsync(id, userId);
if (result.IsSuccess)
return NoContent();
return BadRequest(result.Error);
}
[Authorize(Policy = "RequireAdminRole")]
[HttpPut("{id}/verify")]
public async Task<IActionResult> VerifyReview(int id)
{
var result = await _reviewService.VerifyReviewAsync(id);
if (result.IsSuccess)
return Ok();
return BadRequest(result.Error);
}
Step 10: Notification System (3-4 Days) 📱
1. تنفيذ Notification Service
التنفيذ:
- إنشاء الـ notification models
- تنفيذ الـ notification service
- إعداد Email notifications
طريقة التفكير:
public class NotificationDto
{
public int Id { get; set; }
public string Type { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public bool IsRead { get; set; }
public string Link { get; set; }
public DateTime CreatedAt { get; set; }
}
public interface INotificationService
{
Task<PagedResult<NotificationDto>> GetUserNotificationsAsync(string userId, int pageNumber, int pageSize);
Task<Result> MarkNotificationAsReadAsync(int notificationId, string userId);
Task<Result> MarkAllNotificationsAsReadAsync(string userId);
Task SendBookingNotificationAsync(Booking booking);
Task SendBookingStatusChangedNotificationAsync(Booking booking);
Task SendNewTripNotificationAsync(Trip trip);
}
public class NotificationService : INotificationService
{
private readonly INotificationRepository _notificationRepository;
private readonly IEmailService _emailService;
private readonly IUserRepository _userRepository;
public async Task SendBookingNotificationAsync(Booking booking)
{
// إرسال إشعار للمستخدم
var userNotification = new Notification
{
RecipientId = booking.UserId,
Type = "BookingCreated",
Title = "Booking Confirmation",
Content = $"Your booking for trip to {booking.Trip.Destination} has been received and is being processed.",
IsRead = false,
Link = $"/bookings/{booking.Id}",
CreatedAt = DateTime.UtcNow
};
await _notificationRepository.AddAsync(userNotification);
// إرسال إشعار للوكالة
var agencyNotification = new Notification
{
RecipientId = booking.AgencyId,
Type = "NewBooking",
Title = "New Booking Received",
Content = $"You have received a new booking for trip to {booking.Trip.Destination}.",
IsRead = false,
Link = $"/agency/bookings/{booking.Id}",
CreatedAt = DateTime.UtcNow
};
await _notificationRepository.AddAsync(agencyNotification);
// إرسال إيميل للمستخدم
var user = await _userRepository.GetByIdAsync(booking.UserId);
await _emailService.SendBookingConfirmationEmailAsync(
user.Email,
booking.Id,
booking.Trip.Title,
booking.Trip.Destination,
booking.Trip.StartDate,
booking.Trip.EndDate,
booking.TotalPrice);
}
// باقي التنفيذات
}
2. تنفيذ Email Service
التنفيذ:
public interface IEmailService
{
Task SendEmailAsync(string to, string subject, string htmlContent);
Task SendBookingConfirmationEmailAsync(string to, int bookingId, string tripTitle, string destination, DateTime startDate, DateTime endDate, decimal totalPrice);
Task SendBookingStatusChangedEmailAsync(string to, int bookingId, string tripTitle, string status);
Task SendPasswordResetEmailAsync(string to, string resetLink);
}
public class EmailService : IEmailService
{
private readonly IConfiguration _configuration;
private readonly ILogger<EmailService> _logger;
public EmailService(IConfiguration configuration, ILogger<EmailService> logger)
{
_configuration = configuration;
_logger = logger;
}
public async Task SendEmailAsync(string to, string subject, string htmlContent)
{
var apiKey = _configuration["SendGrid:ApiKey"];
var client = new SendGridClient(apiKey);
var from = new EmailAddress(_configuration["SendGrid:FromEmail"], "Hamla Travel");
var toEmail = new EmailAddress(to);
var msg = MailHelper.CreateSingleEmail(from, toEmail, subject, null, htmlContent);
try
{
var response = await client.SendEmailAsync(msg);
if (response.StatusCode != System.Net.HttpStatusCode.Accepted && response.StatusCode != System.Net.HttpStatusCode.OK)
{
_logger.LogWarning($"Failed to send email to {to}. Status code: {response.StatusCode}");
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error sending email to {to}");
throw;
}
}
public async Task SendBookingConfirmationEmailAsync(string to, int bookingId, string tripTitle, string destination, DateTime startDate, DateTime endDate, decimal totalPrice)
{
var subject = $"Booking Confirmation #{bookingId}";
var content = $@"
<h1>Booking Confirmation</h1>
<p>Thank you for booking with Hamla Travel!</p>
<h2>Booking Details</h2>
<ul>
<li><strong>Booking ID:</strong> {bookingId}</li>
<li><strong>Trip:</strong> {tripTitle}</li>
<li><strong>Destination:</strong> {destination}</li>
<li><strong>Start Date:</strong> {startDate:dd/MM/yyyy}</li>
<li><strong>End Date:</strong> {endDate:dd/MM/yyyy}</li>
<li><strong>Total Price:</strong> ${totalPrice}</li>
</ul>
<p>You can view your booking details anytime by visiting your <a href='{_configuration["AppUrl"]}/bookings/{bookingId}'>booking page</a>.</p>
<p>For any questions, please contact our support team.</p>
<p>Enjoy your trip!</p>
<p>Best regards,<br>Hamla Travel Team</p>
";
await SendEmailAsync(to, subject, content);
}
// باقي التنفيذات
}
3. تنفيذ Notification Controller
التنفيذ:
[Authorize]
[ApiController]
[Route("api/notifications")]
public class NotificationController : ControllerBase
{
private readonly INotificationService _notificationService;
public NotificationController(INotificationService notificationService)
{
_notificationService = notificationService;
}
[HttpGet]
public async Task<IActionResult> GetNotifications(
[FromQuery] int pageNumber = 1,
[FromQuery] int pageSize = 20)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var notifications = await _notificationService.GetUserNotificationsAsync(userId, pageNumber, pageSize);
return Ok(notifications);
}
[HttpPut("{id}/read")]
public async Task<IActionResult> MarkAsRead(int id)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var result = await _notificationService.MarkNotificationAsReadAsync(id, userId);
if (result.IsSuccess)
return Ok();
return BadRequest(result.Error);
}
[HttpPut("read-all")]
public async Task<IActionResult> MarkAllAsRead()
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var result = await _notificationService.MarkAllNotificationsAsReadAsync(userId);
if (result.IsSuccess)
return Ok();
return BadRequest(result.Error);
}
}
Step 11: Admin Management System (4-6 Days) 👨💼
1. تنفيذ Admin Services
التنفيذ:
- إنشاء الـ admin DTOs
- تنفيذ الـ admin services للتحكم في المستخدمين والوكالات
- تنفيذ الـ system monitoring
طريقة التفكير:
public interface IAdminService
{
Task<PagedResult<UserDto>> GetAllUsersAsync(string searchTerm, string role, int pageNumber, int pageSize);
Task<PagedResult<AgencyDto>> GetAllAgenciesAsync(string searchTerm, bool? isVerified, int pageNumber, int pageSize);
Task<Result> VerifyAgencyAsync(int agencyId);
Task<Result> DisableUserAsync(string userId);
Task<Result> EnableUserAsync(string userId);
Task<Result> AssignRoleToUserAsync(string userId, string role);
Task<SystemStatsDto> GetSystemStatsAsync();
}
public class AdminService : IAdminService
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IAgencyProfileRepository _agencyRepository;
private readonly ITripRepository _tripRepository;
private readonly IBookingRepository _bookingRepository;
public async Task<PagedResult<UserDto>> GetAllUsersAsync(string searchTerm, string role, int pageNumber, int pageSize)
{
IQueryable<ApplicationUser> usersQuery = _userManager.Users;
// تطبيق البحث
if (!string.IsNullOrEmpty(searchTerm))
{
searchTerm = searchTerm.ToLower();
usersQuery = usersQuery.Where(u =>
u.Email.ToLower().Contains(searchTerm) ||
u.UserName.ToLower().Contains(searchTerm));
}
// تطبيق فلتر الدور
if (!string.IsNullOrEmpty(role))
{
var usersInRole = await _userManager.GetUsersInRoleAsync(role);
var userIds = usersInRole.Select(u => u.Id);
usersQuery = usersQuery.Where(u => userIds.Contains(u.Id));
}
// حساب العدد الإجمالي
var totalCount = await usersQuery.CountAsync();
// تطبيق التصفح
var users = await usersQuery
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
// تحويل النتائج إلى DTOs
var userDtos = new List<UserDto>();
foreach (var user in users)
{
var roles = await _userManager.GetRolesAsync(user);
userDtos.Add(new UserDto
{
Id = user.Id,
UserName = user.UserName,
Email = user.Email,
PhoneNumber = user.PhoneNumber,
UserType = user.UserType,
IsEmailConfirmed = user.EmailConfirmed,
IsPhoneConfirmed = user.PhoneNumberConfirmed,
Roles = roles.ToList(),
CreatedAt = user.CreatedAt
});
}
return new PagedResult<UserDto>
{
Items = userDtos,
TotalCount = totalCount,
PageNumber = pageNumber,
PageSize = pageSize
};
}
public async Task<SystemStatsDto> GetSystemStatsAsync()
{
var totalUsers = await _userManager.Users.CountAsync(u => u.UserType == "Traveler");
var totalAgencies = await _userManager.Users.CountAsync(u => u.UserType == "Agency");
var totalTrips = await _tripRepository.CountAsync();
var totalBookings = await _bookingRepository.CountAsync();
var pendingBookings = await _bookingRepository.CountAsync(b => b.Status == "Pending");
var recentBookings = await _bookingRepository.GetRecentBookingsAsync(5);
// حساب الإيرادات
var revenue = await _bookingRepository.CalculateTotalRevenueAsync();
return new SystemStatsDto
{
TotalUsers = totalUsers,
TotalAgencies = totalAgencies,
TotalTrips = totalTrips,
TotalBookings = totalBookings,
PendingBookings = pendingBookings,
TotalRevenue = revenue,
RecentBookings = _mapper.Map<List<BookingDto>>(recentBookings)
};
}
// باقي التنفيذات
}
2. تنفيذ Admin Controller
التنفيذ:
[Authorize(Policy = "RequireAdminRole")]
[ApiController]
[Route("api/admin")]
public class AdminController : ControllerBase
{
private readonly IAdminService _adminService;
public AdminController(IAdminService adminService)
{
_adminService = adminService;
}
[HttpGet("users")]
public async Task<IActionResult> GetUsers(
[FromQuery] string searchTerm = null,
[FromQuery] string role = null,
[FromQuery] int pageNumber = 1,
[FromQuery] int pageSize = 10)
{
var users = await _adminService.GetAllUsersAsync(searchTerm, role, pageNumber, pageSize);
return Ok(users);
}
[HttpGet("agencies")]
public async Task<IActionResult> GetAgencies(
[FromQuery] string searchTerm = null,
[FromQuery] bool? isVerified = null,
[FromQuery] int pageNumber = 1,
[FromQuery] int pageSize = 10)
{
var agencies = await _adminService.GetAllAgenciesAsync(searchTerm, isVerified, pageNumber, pageSize);
return Ok(agencies);
}
[HttpPost("agencies/{agencyId}/verify")]
public async Task<IActionResult> VerifyAgency(int agencyId)
{
var result = await _adminService.VerifyAgencyAsync(agencyId);
if (result.IsSuccess)
return Ok();
return BadRequest(result.Error);
}
[HttpPost("users/{userId}/disable")]
public async Task<IActionResult> DisableUser(string userId)
{
var result = await _adminService.DisableUserAsync(userId);
if (result.IsSuccess)
return Ok();
return BadRequest(result.Error);
}
[HttpPost("users/{userId}/enable")]
public async Task<IActionResult> EnableUser(string userId)
{
var result = await _adminService.EnableUserAsync(userId);
if (result.IsSuccess)
return Ok();
return BadRequest(result.Error);
}
[HttpPost("users/{userId}/roles")]
public async Task<IActionResult> AssignRole(string userId, [FromBody] AssignRoleDto model)
{
var result = await _adminService.AssignRoleToUserAsync(userId, model.Role);
if (result.IsSuccess)
return Ok();
return BadRequest(result.Error);
}
[HttpGet("stats")]
public async Task<IActionResult> GetStats()
{
var stats = await _adminService.GetSystemStatsAsync();
return Ok(stats);
}
}
Step 12: Security and Error Handling (3-4 Days) 🔒
1. تنفيذ Global Exception Handler
التنفيذ:
public class ErrorDetails
{
public int StatusCode { get; set; }
public string Message { get; set; }
public string Details { get; set; }
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
}
public class GlobalExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<GlobalExceptionMiddleware> _logger;
private readonly IWebHostEnvironment _env;
public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger, IWebHostEnvironment env)
{
_next = next;
_logger = logger;
_env = env;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
await HandleExceptionAsync(context, ex);
}
}
private Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
var statusCode = exception switch
{
UnauthorizedAccessException => StatusCodes.Status401Unauthorized,
ArgumentException => StatusCodes.Status400BadRequest,
KeyNotFoundException => StatusCodes.Status404NotFound,
_ => StatusCodes.Status500InternalServerError
};
context.Response.StatusCode = statusCode;
var response = _env.IsDevelopment()
? new ErrorDetails
{
StatusCode = statusCode,
Message = exception.Message,
Details = exception.StackTrace
}
: new ErrorDetails
{
StatusCode = statusCode,
Message = exception.Message,
Details = "Contact support for more details"
};
return context.Response.WriteAsync(response.ToString());
}
}
// في Startup.cs أو Program.cs
app.UseMiddleware<GlobalExceptionMiddleware>();
2. تنفيذ Input Validation
التنفيذ:
// تنفيذ validation filter
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var errors = context.ModelState
.Where(e => e.Value.Errors.Count > 0)
.Select(e => new
{
Field = e.Key,
Errors = e.Value.Errors.Select(er => er.ErrorMessage).ToArray()
})
.ToArray();
context.Result = new BadRequestObjectResult(new
{
success = false,
errors = errors
});
}
}
}
// إضافة الـ filter في Startup.cs
services.AddControllers(options =>
{
options.Filters.Add(new ValidateModelAttribute());
});
// مثال على استخدام FluentValidation للـ validation
public class CreateTripValidator : AbstractValidator<CreateTripDto>
{
public CreateTripValidator()
{
RuleFor(x => x.Title)
.NotEmpty().WithMessage("Title is required")
.MaximumLength(100).WithMessage("Title cannot exceed 100 characters");
RuleFor(x => x.Destination)
.NotEmpty().WithMessage("Destination is required")
.MaximumLength(100).WithMessage("Destination cannot exceed 100 characters");
RuleFor(x => x.StartDate)
.NotEmpty().WithMessage("Start date is required")
.GreaterThan(DateTime.Now).WithMessage("Start date must be in the future");
RuleFor(x => x.EndDate)
.NotEmpty().WithMessage("End date is required")
.GreaterThan(x => x.StartDate).WithMessage("End date must be after start date");
RuleFor(x => x.Price)
.NotEmpty().WithMessage("Price is required")
.GreaterThan(0).WithMessage("Price must be greater than zero");
RuleFor(x => x.MaxTravelers)
.NotEmpty().WithMessage("Max travelers is required")
.GreaterThan(0).WithMessage("Max travelers must be greater than zero")
.LessThanOrEqualTo(100).WithMessage("Max travelers cannot exceed 100");
}
}
// تسجيل الـ validators في Startup.cs
services.AddFluentValidationAutoValidation();
services.AddValidatorsFromAssemblyContaining<CreateTripValidator>();
3. تنفيذ Security Headers and HTTPS
التنفيذ:
// في Startup.cs أو Program.cs
app.UseHttpsRedirection();
// إضافة الـ security headers
app.Use(async (context, next) =>
{
context.Response.Headers.Add("X-XSS-Protection", "1; mode=block");
context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
context.Response.Headers.Add("X-Frame-Options", "DENY");
context.Response.Headers.Add("Referrer-Policy", "no-referrer-when-downgrade");
context.Response.Headers.Add("Content-Security-Policy", "default-src 'self'");
// HSTS Header
context.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
await next();
});
// إعداد CORS
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", policy =>
{
policy.WithOrigins("https://hamla-app.com", "https://admin.hamla-app.com")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
app.UseCors("CorsPolicy");
4. تنفيذ Rate Limiting
التنفيذ:
// إضافة middleware للـ rate limiting
public class RateLimitingMiddleware
{
private readonly RequestDelegate _next;
private readonly IMemoryCache _cache;
private readonly ILogger<RateLimitingMiddleware> _logger;
// قيود الـ rate limiting
private readonly int _requestLimit = 100; // عدد الطلبات المسموح بها
private readonly int _timeWindowMinutes = 15; // الفترة الزمنية بالدقائق
public RateLimitingMiddleware(RequestDelegate next, IMemoryCache cache, ILogger<RateLimitingMiddleware> logger)
{
_next = next;
_cache = cache;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
// الحصول على IP المستخدم
var ipAddress = context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
var cacheKey = $"RateLimit_{ipAddress}";
// التحقق من سجل الطلبات
var requestLog = _cache.Get<List<DateTime>>(cacheKey) ?? new List<DateTime>();
// حذف السجلات القديمة
var timeWindowStart = DateTime.UtcNow.AddMinutes(-_timeWindowMinutes);
requestLog.RemoveAll(d => d < timeWindowStart);
// التحقق من تجاوز الحد
if (requestLog.Count >= _requestLimit)
{
_logger.LogWarning($"Rate limit exceeded for IP: {ipAddress}");
context.Response.StatusCode = 429; // Too Many Requests
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonSerializer.Serialize(new
{
error = "Too many requests. Please try again later."
}));
return;
}
// إضافة السجل الحالي
requestLog.Add(DateTime.UtcNow);
// تحديث الـ cache
_cache.Set(cacheKey, requestLog, TimeSpan.FromMinutes(_timeWindowMinutes));
// المتابعة للـ middleware التالي
await _next(context);
}
}
// إضافة الـ middleware في Startup.cs
app.UseMiddleware<RateLimitingMiddleware>();
Step 13: Performance Optimization (3-4 Days) ⚡
1. تنفيذ Caching
التنفيذ:
// إضافة Redis Cache
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = Configuration.GetConnectionString("RedisConnection");
options.InstanceName = "HamlaCache_";
});
// تنفيذ الـ Cache Service
public interface ICacheService
{
Task<T> GetAsync<T>(string key);
Task SetAsync<T>(string key, T value, TimeSpan? expiry = null);
Task RemoveAsync(string key);
}
public class RedisCacheService : ICacheService
{
private readonly IDistributedCache _cache;
private readonly ILogger<RedisCacheService> _logger;
public RedisCacheService(IDistributedCache cache, ILogger<RedisCacheService> logger)
{
_cache = cache;
_logger = logger;
}
public async Task<T> GetAsync<T>(string key)
{
try
{
var cachedValue = await _cache.GetStringAsync(key);
if (string.IsNullOrEmpty(cachedValue))
return default;
return JsonSerializer.Deserialize<T>(cachedValue);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error getting cached value for key: {key}");
return default;
}
}
public async Task SetAsync<T>(string key, T value, TimeSpan? expiry = null)
{
try
{
var options = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = expiry ?? TimeSpan.FromMinutes(30)
};
var serializedValue = JsonSerializer.Serialize(value);
await _cache.SetStringAsync(key, serializedValue, options);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error setting cached value for key: {key}");
}
}
public async Task RemoveAsync(string key)
{
try
{
await _cache.RemoveAsync(key);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error removing cached value for key: {key}");
}
}
}
// استخدام الـ cache في الـ services
public class TripService : ITripService
{
private readonly ITripRepository _tripRepository;
private readonly ICacheService _cacheService;
// في دالة GetAllTripsAsync
public async Task<IEnumerable<TripDto>> GetAllTripsAsync(TripFilterDto filter)
{
// إنشاء مفتاح الـ cache بناءً على مدخلات الفلتر
var cacheKey = $"trips_{filter.Destination}_{filter.StartDateFrom}_{filter.StartDateTo}_{filter.MinPrice}_{filter.MaxPrice}_{filter.PageNumber}_{filter.PageSize}_{filter.SortBy}_{filter.SortAscending}";
// محاولة استرجاع النتائج من الـ cache
var cachedResult = await _cacheService.GetAsync<PagedResult<TripDto>>(cacheKey);
if (cachedResult != null)
return cachedResult;
// إذا لم تكن النتائج موجودة في الـ cache، جلبها من قاعدة البيانات
var trips = await _tripRepository.GetTripsAsync(filter);
var tripsDto = _mapper.Map<PagedResult<TripDto>>(trips);
// تخزين النتائج في الـ cache
await _cacheService.SetAsync(cacheKey, tripsDto, TimeSpan.FromMinutes(10));
return tripsDto;
}
}
2. تنفيذ Query Optimization
التنفيذ:
// تحسين الاستعلامات باستخدام Eager Loading المناسب
public async Task<PagedResult<Trip>> GetTripsAsync(TripFilterDto filter)
{
var query = _context.Trips
.AsNoTracking() // لتحسين الأداء عندما نحتاج للقراءة فقط
.Include(t => t.Agency)
.ThenInclude(a => a.AgencyProfile) // تضمين البيانات المرتبطة
.Include(t => t.TripImages.Where(i => i.IsMain)) // تحميل الصور الرئيسية فقط
.AsQueryable();
// تطبيق الفلاتر...
// تطبيق الترتيب
query = ApplySorting(query, filter.SortBy, filter.SortAscending);
// التصفح بطريقة محسنة
int totalCount = await query.CountAsync();
// تحسين استعلام التصفح
var items = await query
.Skip((filter.PageNumber - 1) * filter.PageSize)
.Take(filter.PageSize)
.ToListAsync();
return new PagedResult<Trip>
{
Items = items,
TotalCount = totalCount,
PageNumber = filter.PageNumber,
PageSize = filter.PageSize
};
}
// إنشاء فهارس (Indexes) إضافية في الـ DbContext
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// تحسين البحث عن الرحلات
builder.Entity<Trip>()
.HasIndex(t => t.Destination);
builder.Entity<Trip>()
.HasIndex(t => t.StartDate);
builder.Entity<Trip>()
.HasIndex(t => t.Price);
// تحسين البحث عن الحجوزات
builder.Entity<Booking>()
.HasIndex(b => b.UserId);
builder.Entity<Booking>()
.HasIndex(b => b.AgencyId);
builder.Entity<Booking>()
.HasIndex(b => b.Status);
}
3. تنفيذ Response Compression
التنفيذ:
// في Startup.cs
services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
});
services.Configure<BrotliCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});
services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});
// تطبيق الـ middleware
app.UseResponseCompression();
Step 14: Testing (5-7 Days) 🧪
1. تنفيذ Unit Tests
التنفيذ:
// اختبار AuthService
[Fact]
public async Task RegisterUser_WithValidData_ReturnsSuccess()
{
// Arrange
var userManagerMock = new Mock<UserManager<ApplicationUser>>(/* dependencies */);
var roleManagerMock = new Mock<RoleManager<IdentityRole>>(/* dependencies */);
userManagerMock.Setup(m => m.FindByEmailAsync(It.IsAny<string>()))
.ReturnsAsync((ApplicationUser)null);
userManagerMock.Setup(m => m.CreateAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()))
.ReturnsAsync(IdentityResult.Success);
userManagerMock.Setup(m => m.AddToRoleAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()))
.ReturnsAsync(IdentityResult.Success);
var authService = new AuthService(userManagerMock.Object, roleManagerMock.Object, /* other dependencies */);
var registerDto = new RegisterDto
{
Email = "test@example.com",
Password = "Password123!",
FirstName = "Test",
LastName = "User"
};
// Act
var result = await authService.RegisterUserAsync(registerDto);
// Assert
Assert.True(result.IsSuccess);
Assert.NotNull(result.Data.Token);
userManagerMock.Verify(m => m.CreateAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()), Times.Once);
userManagerMock.Verify(m => m.AddToRoleAsync(It.IsAny<ApplicationUser>(), "Traveler"), Times.Once);
}
// اختبار TripService
[Fact]
public async Task CreateTrip_WithValidData_ReturnsSuccess()
{
// Arrange
var tripRepositoryMock = new Mock<ITripRepository>();
var userRepositoryMock = new Mock<IUserRepository>();
var mapperMock = new Mock<IMapper>();
userRepositoryMock.Setup(m => m.GetAgencyByIdAsync(It.IsAny<string>()))
.ReturnsAsync(new AgencyProfile { Id = 1, UserId = "agency1" });
tripRepositoryMock.Setup(m => m.AddAsync(It.IsAny<Trip>()))
.ReturnsAsync(1);
var tripService = new TripService(
tripRepositoryMock.Object,
userRepositoryMock.Object,
mapperMock.Object,
/* other dependencies */);
var createTripDto = new CreateTripDto
{
Title = "Trip to Sharm El Sheikh",
Destination = "Sharm El Sheikh",
StartDate = DateTime.Now.AddDays(10),
EndDate = DateTime.Now.AddDays(15),
Price = 2500,
MaxTravelers = 20,
Description = "Enjoy 5 days in Sharm El Sheikh"
};
// Act
var result = await tripService.CreateTripAsync("agency1", createTripDto);
// Assert
Assert.True(result.IsSuccess);
Assert.Equal(1, result.Data);
tripRepositoryMock.Verify(m => m.AddAsync(It.IsAny<Trip>()), Times.Once);
}
2. تنفيذ Integration Tests
التنفيذ:
public class TripControllerIntegrationTests : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _factory;
private readonly HttpClient _client;
public TripControllerIntegrationTests(WebApplicationFactory<Startup> factory)
{
_factory = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// استبدال قاعدة البيانات الحقيقية بقاعدة بيانات ذاكرة للاختبار
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<ApplicationDbContext>));
if (descriptor != null)
{
services.Remove(descriptor);
}
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseInMemoryDatabase("TestDatabase");
});
// إضافة بيانات اختبار
var sp = services.BuildServiceProvider();
using var scope = sp.CreateScope();
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<ApplicationDbContext>();
db.Database.EnsureCreated();
// إضافة بيانات الاختبار
SeedTestData(db);
});
});
_client = _factory.CreateClient();
}
[Fact]
public async Task GetTrips_ReturnsSuccessAndCorrectContentType()
{
// Arrange
var request = "/api/trips";
// Act
var response = await _client.GetAsync(request);
// Assert
response.EnsureSuccessStatusCode();
Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString());
}
[Fact]
public async Task GetTrip_WithValidId_ReturnsTrip()
{
// Arrange
var request = "/api/trips/1";
// Act
var response = await _client.GetAsync(request);
// Assert
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
var trip = JsonSerializer.Deserialize<TripDto>(content, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
Assert.NotNull(trip);
Assert.Equal(1, trip.Id);
Assert.Equal("Trip to Sharm El Sheikh", trip.Title);
}
[Fact]
public async Task CreateTrip_WithValidData_ReturnsCreated()
{
// Arrange
var loginResponse = await _client.PostAsJsonAsync("/api/auth/login", new
{
Email = "agency@example.com",
Password = "Agency123!"
});
var loginContent = await loginResponse.Content.ReadAsStringAsync();
var loginResult = JsonSerializer.Deserialize<AuthResultDto>(loginContent, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginResult.Token);
var tripDto = new
{
Title = "New Test Trip",
Destination = "Alexandria",
StartDate = DateTime.Now.AddDays(10).ToString("yyyy-MM-dd"),
EndDate = DateTime.Now.AddDays(15).ToString("yyyy-MM-dd"),
Price = 1500,
MaxTravelers = 15,
Description = "A test trip to Alexandria"
};
// Act
var response = await _client.PostAsJsonAsync("/api/trips", tripDto);
// Assert
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
Assert.NotNull(response.Headers.Location);
}
private void SeedTestData(ApplicationDbContext context)
{
// إضافة مستخدم وكالة للاختبار
var agency = new ApplicationUser
{
Id = "agency1",
UserName = "agency@example.com",
Email = "agency@example.com",
EmailConfirmed = true,
UserType = "Agency",
CreatedAt = DateTime.UtcNow
};
var hasher = new PasswordHasher<ApplicationUser>();
agency.PasswordHash = hasher.HashPassword(agency, "Agency123!");
context.Users.Add(agency);
// إضافة ملف وكالة
context.AgencyProfiles.Add(new AgencyProfile
{
Id = 1,
UserId = "agency1",
CompanyName = "Test Travel Agency",
Description = "A test agency",
IsVerified = true
});
// إضافة رحلات للاختبار
context.Trips.Add(new Trip
{
Id = 1,
AgencyId = 1,
Title = "Trip to Sharm El Sheikh",
Destination = "Sharm El Sheikh",
StartDate = DateTime.Now.AddDays(10),
EndDate = DateTime.Now.AddDays(15),
Price = 2500,
Description = "Enjoy 5 days in Sharm El Sheikh",
MaxTravelers = 20,
AvailableSpots = 20,
Status = "Active",
CreatedAt = DateTime.UtcNow
});
context.SaveChanges();
}
3. تنفيذ API Tests
التنفيذ:
// استخدام Postman Collections للاختبار المتكامل للـ API
// نموذج لملف Postman Collection
/*
{
"info": {
"name": "Hamla API Tests",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Authentication",
"item": [
{
"name": "Register User",
"request": {
"method": "POST",
"url": "{{baseUrl}}/api/auth/register",
"body": {
"mode": "raw",
"raw": "{\n \"email\": \"test@example.com\",\n \"password\": \"Password123!\",\n \"firstName\": \"Test\",\n \"lastName\": \"User\",\n \"phoneNumber\": \"+201234567890\"\n}",
"options": {
"raw": {
"language": "json"
}
}
}
}
},
{
"name": "Login User",
"request": {
"method": "POST",
"url": "{{baseUrl}}/api/auth/login",
"body": {
"mode": "raw",
"raw": "{\n \"email\": \"test@example.com\",\n \"password\": \"Password123!\"\n}",
"options": {
"raw": {
"language": "json"
}
}
}
}
}
]
},
{
"name": "Trips",
"item": [
{
"name": "Get Trips",
"request": {
"method": "GET",
"url": "{{baseUrl}}/api/trips",
"query": [
{
"key": "destination",
"value": "Sharm"
},
{
"key": "startDateFrom",
"value": "2023-06-01"
},
{
"key": "pageNumber",
"value": "1"
},
{
"key": "pageSize",
"value": "10"
}
]
}
},
{
"name": "Get Trip by ID",
"request": {
"method": "GET",
"url": "{{baseUrl}}/api/trips/1"
}
},
{
"name": "Create Trip",
"request": {
"method": "POST",
"url": "{{baseUrl}}/api/trips",
"header": [
{
"key": "Authorization",
"value": "Bearer {{agencyToken}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"title\": \"Trip to Alexandria\",\n \"destination\": \"Alexandria\",\n \"startDate\": \"2023-08-15\",\n \"endDate\": \"2023-08-20\",\n \"price\": 1800,\n \"maxTravelers\": 15,\n \"description\": \"Enjoy 5 days in Alexandria\"\n}",
"options": {
"raw": {
"language": "json"
}
}
}
}
}
]
}
],
"variable": [
{
"key": "baseUrl",
"value": "https://localhost:5001"
},
{
"key": "userToken",
"value": ""
},
{
"key": "agencyToken",
"value": ""
}
]
}
*/
Step 15: Deployment and DevOps (3-5 Days) 🚀
1. إعداد CI/CD Pipeline
التنفيذ:
# GitHub Actions workflow for CI/CD
# .github/workflows/dotnet.yml
name: Hamla API CI/CD
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal
deploy-staging:
needs: build
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
- name: Publish
run: dotnet publish Hamla.API/Hamla.API.csproj -c Release -o ./publish
- name: Deploy to Azure Web App (Staging)
uses: azure/webapps-deploy@v2
with:
app-name: 'hamla-api-staging'
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE_STAGING }}
package: ./publish
deploy-production:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
- name: Publish
run: dotnet publish Hamla.API/Hamla.API.csproj -c Release -o ./publish
- name: Deploy to Azure Web App (Production)
uses: azure/webapps-deploy@v2
with:
app-name: 'hamla-api-production'
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE_PRODUCTION }}
package: ./publish
2. إعداد Environment Variables
التنفيذ:
// appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=Hamla;Trusted_Connection=True;MultipleActiveResultSets=true",
"RedisConnection": "localhost:6379"
},
"JWT": {
"Secret": "PLACEHOLDER_SECRET_THAT_MUST_BE_REPLACED_IN_PRODUCTION",
"Issuer": "HamlaApi",
"Audience": "HamlaClients",
"DurationInMinutes": 60
},
"Email": {
"SendGrid": {
"ApiKey": "PLACEHOLDER_API_KEY",
"FromEmail": "noreply@hamla-travel.com",
"FromName": "Hamla Travel"
}
},
"Storage": {
"Azure": {
"ConnectionString": "PLACEHOLDER_CONNECTION_STRING",
"ContainerName": "hamla-images"
}
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
// Program.cs - قراءة الـ environment variables
var builder = WebApplication.CreateBuilder(args);
// تجاوز الإعدادات من الـ environment variables
if (!builder.Environment.IsDevelopment())
{
builder.Configuration["ConnectionStrings:DefaultConnection"] = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING");
builder.Configuration["ConnectionStrings:RedisConnection"] = Environment.GetEnvironmentVariable("REDIS_CONNECTION_STRING");
builder.Configuration["JWT:Secret"] = Environment.GetEnvironmentVariable("JWT_SECRET");
builder.Configuration["Email:SendGrid:ApiKey"] = Environment.GetEnvironmentVariable("SENDGRID_API_KEY");
builder.Configuration["Storage:Azure:ConnectionString"] = Environment.GetEnvironmentVariable("AZURE_STORAGE_CONNECTION_STRING");
}
3. إعداد Docker
التنفيذ:
# Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["Hamla.API/Hamla.API.csproj", "Hamla.API/"]
COPY ["Hamla.Core/Hamla.Core.csproj", "Hamla.Core/"]
COPY ["Hamla.Infrastructure/Hamla.Infrastructure.csproj", "Hamla.Infrastructure/"]
COPY ["Hamla.Shared/Hamla.Shared.csproj", "Hamla.Shared/"]
RUN dotnet restore "Hamla.API/Hamla.API.csproj"
COPY . .
WORKDIR "/src/Hamla.API"
RUN dotnet build "Hamla.API.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "Hamla.API.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Hamla.API.dll"]
# docker-compose.yml
version: '3.8'
services:
api:
build:
context: .
dockerfile: Dockerfile
ports:
- "5000:80"
- "5001:443"
depends_on:
- db
- redis
environment:
- ASPNETCORE_ENVIRONMENT=Production
- DB_CONNECTION_STRING=Server=db;Database=Hamla;User=sa;Password=${DB_PASSWORD};
- REDIS_CONNECTION_STRING=redis:6379
- JWT_SECRET=${JWT_SECRET}
- SENDGRID_API_KEY=${SENDGRID_API_KEY}
- AZURE_STORAGE_CONNECTION_STRING=${AZURE_STORAGE_CONNECTION_STRING}
networks:
- hamla-network
db:
image: mcr.microsoft.com/mssql/server:2019-latest
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=${DB_PASSWORD}
ports:
- "1433:1433"
volumes:
- hamla-data:/var/opt/mssql
networks:
- hamla-network
redis:
image: redis:alpine
ports:
- "6379:6379"
volumes:
- hamla-redis:/data
networks:
- hamla-network
networks:
hamla-network:
driver: bridge
volumes:
hamla-data:
hamla-redis:
4. إعداد Monitoring
التنفيذ:
// Program.cs - إضافة Application Insights
builder.Services.AddApplicationInsightsTelemetry();
// تكوين Serilog للتسجيل
var logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File("logs/hamla-.txt", rollingInterval: RollingInterval.Day)
.WriteTo.ApplicationInsights(TelemetryConfiguration.Active, TelemetryConverter.Traces)
.CreateLogger();
builder.Logging.ClearProviders();
builder.Logging.AddSerilog(logger);
Step 16: Documentation and API Versioning (2-3 Days) 📚
1. إعداد Swagger للـ API Documentation
التنفيذ:
// Program.cs - إعداد Swagger
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Hamla Travel API",
Version = "v1",
Description = "API for Hamla Travel Platform",
Contact = new OpenApiContact
{
Name = "Hamla Development Team",
Email = "dev@hamla-travel.com",
Url = new Uri("https://hamla-travel.com/contact")
}
});
// إضافة ملفات XML تعليقات
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
// إعداد الـ JWT authentication في Swagger
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\""
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] {}
}
});
// تجميع الـ endpoints بواسطة الـ controllers
c.TagActionsBy(api => new[] { api.GroupName ?? api.ActionDescriptor.RouteValues["controller"] });
c.DocInclusionPredicate((name, api) => true);
});
// إضافة Swagger middleware
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Hamla API v1");
c.RoutePrefix = "api-docs";
c.DocumentTitle = "Hamla API Documentation";
c.DocExpansion(DocExpansion.None);
});
2. إعداد API Versioning
التنفيذ:
// Program.cs - إعداد API versioning
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = ApiVersionReader.Combine(
new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("X-API-Version"),
new QueryStringApiVersionReader("api-version"));
});
builder.Services.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
// تكوين Swagger للعمل مع API versioning (continued)
builder.Services.AddSwaggerGen(c =>
{
// إعداد الإصدار الأول
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Hamla Travel API v1",
Version = "v1",
Description = "API for Hamla Travel Platform - Version 1"
});
// إعداد الإصدار الثاني
c.SwaggerDoc("v2", new OpenApiInfo
{
Title = "Hamla Travel API v2",
Version = "v2",
Description = "API for Hamla Travel Platform - Version 2"
});
// تكوين operation filter للتعامل مع versions
c.OperationFilter<SwaggerDefaultValues>();
// باقي التكوينات...
});
// تكوين SwaggerUI لعرض الإصدارات المختلفة
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Hamla API v1");
c.SwaggerEndpoint("/swagger/v2/swagger.json", "Hamla API v2");
c.RoutePrefix = "api-docs";
});
// مثال على controller مع versioning
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/trips")]
public class TripController : ControllerBase
{
// إصدار 1.0 من الـ API
}
[ApiController]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/trips")]
public class TripV2Controller : ControllerBase
{
// إصدار 2.0 من الـ API مع ميزات إضافية
}
3. إعداد API Documentation
التنفيذ:
// مثال على استخدام التعليقات XML للتوثيق
/// <summary>
/// Creates a new trip.
/// </summary>
/// <param name="model">The trip data</param>
/// <returns>Returns the created trip's ID</returns>
/// <response code="201">Returns the newly created trip's ID</response>
/// <response code="400">If the model is invalid</response>
/// <response code="401">If the user is not authenticated</response>
/// <response code="403">If the user is not authorized to create trips</response>
[Authorize(Policy = "RequireAgencyRole")]
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> CreateTrip(CreateTripDto model)
{
var agencyId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var result = await _tripService.CreateTripAsync(agencyId, model);
if (result.IsSuccess)
return CreatedAtAction(nameof(GetTrip), new { id = result.Data }, null);
return BadRequest(result.Error);
}
// إعداد README.md للـ API
/*
# Hamla Travel API Documentation
## Introduction
This API provides backend services for the Hamla Travel Platform, connecting travelers with travel agencies and enabling trip bookings.
## Getting Started
### Prerequisites
- .NET 6 SDK
- SQL Server or PostgreSQL
- Redis (for caching)
### Installation
1. Clone the repository
git clone https://github.com/hamla-travel/hamla-api.git
2. Navigate to the project directory
cd hamla-api
3. Restore dependencies
dotnet restore
4. Update the connection strings in appsettings.json
5. Run database migrations
dotnet ef database update
6. Run the application
dotnet run
The API will be available at https://localhost:5001
## API Endpoints
The API documentation is available at https://localhost:5001/api-docs when the application is running.
## Authentication
The API uses JWT Bearer token authentication. To access protected endpoints:
1. Register or login to get a token
2. Include the token in the Authorization header:
Authorization: Bearer {your_token}
## Rate Limiting
API requests are limited to 100 requests per 15 minutes per IP address.
*/
Step 17: Project Management and Final Preparations (2-3 Days) 📊
1. Database Migration Scripts
التنفيذ:
// إنشاء migration مع وصف مناسب
dotnet ef migrations add InitialCreate -o Data/Migrations
// إنشاء migration للإصدار 2
dotnet ef migrations add AddLoyaltyProgram -o Data/Migrations
// إنشاء migration للإصدار 3
dotnet ef migrations add AddPaymentSystem -o Data/Migrations
// إنشاء SQL script للـ migrations
dotnet ef migrations script --output Scripts/migrations.sql
// كتابة script لـ seed data
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading.Tasks;
public static class SeedData
{
public static async Task Initialize(IServiceProvider serviceProvider)
{
using var scope = serviceProvider.CreateScope();
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<ApplicationDbContext>();
var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();
await SeedRoles(roleManager);
await SeedAdmin(userManager);
await SeedTestData(context, userManager);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
}
}
private static async Task SeedRoles(RoleManager<IdentityRole> roleManager)
{
// إنشاء الأدوار إذا لم تكن موجودة
string[] roles = { "Admin", "Agency", "Traveler" };
foreach (var role in roles)
{
if (!await roleManager.RoleExistsAsync(role))
{
await roleManager.CreateAsync(new IdentityRole(role));
}
}
}
private static async Task SeedAdmin(UserManager<ApplicationUser> userManager)
{
// إنشاء حساب الأدمن
var adminEmail = "admin@hamla-travel.com";
var admin = await userManager.FindByEmailAsync(adminEmail);
if (admin == null)
{
admin = new ApplicationUser
{
UserName = adminEmail,
Email = adminEmail,
EmailConfirmed = true,
UserType = "Admin",
CreatedAt = DateTime.UtcNow
};
var result = await userManager.CreateAsync(admin, "Admin123!");
if (result.Succeeded)
{
await userManager.AddToRoleAsync(admin, "Admin");
}
}
}
private static async Task SeedTestData(ApplicationDbContext context, UserManager<ApplicationUser> userManager)
{
// إضافة بيانات تجريبية فقط في بيئة التطوير
if (context.Trips.Any())
return;
// إنشاء حساب وكالة تجريبي
var agencyEmail = "agency@example.com";
var agency = await userManager.FindByEmailAsync(agencyEmail);
if (agency == null)
{
agency = new ApplicationUser
{
UserName = agencyEmail,
Email = agencyEmail,
EmailConfirmed = true,
UserType = "Agency",
CreatedAt = DateTime.UtcNow
};
var result = await userManager.CreateAsync(agency, "Agency123!");
if (result.Succeeded)
{
await userManager.AddToRoleAsync(agency, "Agency");
}
}
// إضافة ملف وكالة
var agencyProfile = new AgencyProfile
{
UserId = agency.Id,
CompanyName = "Example Travel Agency",
Description = "A sample travel agency for testing",
IsVerified = true,
ContactEmail = agencyEmail,
ContactPhone = "+201234567890"
};
context.AgencyProfiles.Add(agencyProfile);
await context.SaveChangesAsync();
// إضافة رحلات تجريبية
var trips = new List<Trip>
{
new Trip
{
AgencyId = agencyProfile.Id,
Title = "Sharm El Sheikh Beach Vacation",
Destination = "Sharm El Sheikh",
StartDate = DateTime.Now.AddDays(10),
EndDate = DateTime.Now.AddDays(15),
Price = 2500,
Description = "Enjoy 5 days in Sharm El Sheikh with full accommodation and activities",
MaxTravelers = 20,
AvailableSpots = 20,
Status = "Active",
CreatedAt = DateTime.UtcNow
},
new Trip
{
AgencyId = agencyProfile.Id,
Title = "Cairo Historical Tour",
Destination = "Cairo",
StartDate = DateTime.Now.AddDays(20),
EndDate = DateTime.Now.AddDays(23),
Price = 1800,
Description = "Explore the historical sites of Cairo including the Pyramids and Egyptian Museum",
MaxTravelers = 15,
AvailableSpots = 15,
Status = "Active",
CreatedAt = DateTime.UtcNow
}
};
context.Trips.AddRange(trips);
await context.SaveChangesAsync();
}
}
2. النسخ الاحتياطي واسترداد البيانات
التنفيذ:
# PowerShell script للنسخ الاحتياطي التلقائي لقاعدة البيانات
# backup-database.ps1
param (
[string]$ServerName = "localhost",
[string]$DatabaseName = "Hamla",
[string]$BackupPath = "D:\Backups\Hamla",
[string]$RetentionDays = 7
)
# إنشاء مجلد النسخ الاحتياطي إذا لم يكن موجوداً
if (-not (Test-Path -Path $BackupPath)) {
New-Item -ItemType Directory -Path $BackupPath | Out-Null
}
# تحديد اسم ملف النسخ الاحتياطي
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$backupFile = Join-Path -Path $BackupPath -ChildPath "$DatabaseName`_$timestamp.bak"
# تنفيذ النسخ الاحتياطي
try {
$query = "BACKUP DATABASE [$DatabaseName] TO DISK = N'$backupFile' WITH NOFORMAT, INIT, NAME = N'$DatabaseName-Full Database Backup', SKIP, NOREWIND, NOUNLOAD, STATS = 10"
Invoke-Sqlcmd -ServerInstance $ServerName -Query $query -QueryTimeout 3600
Write-Output "Database backup completed successfully: $backupFile"
# حذف الملفات القديمة
$cutoffDate = (Get-Date).AddDays(-$RetentionDays)
Get-ChildItem -Path $BackupPath -Filter "$DatabaseName*.bak" | Where-Object { $_.LastWriteTime -lt $cutoffDate } | Remove-Item -Force
Write-Output "Old backup files cleaned up."
} catch {
Write-Error "Error creating database backup: $_"
exit 1
}
# YAML للاستخدام مع Azure DevOps أو GitHub Actions لجدولة عملية النسخ الاحتياطي
# .github/workflows/database-backup.yml
#
# name: Database Backup
#
# on:
# schedule:
# - cron: '0 2 * * *' # تشغيل كل يوم الساعة 2 صباحاً UTC
#
# jobs:
# backup:
# runs-on: windows-latest
#
# steps:
# - name: Checkout repository
# uses: actions/checkout@v2
#
# - name: Run Database Backup Script
# shell: pwsh
# run: ./scripts/backup-database.ps1 -ServerName '${{ secrets.DB_SERVER }}' -DatabaseName 'Hamla' -BackupPath '${{ secrets.BACKUP_PATH }}'
#
# - name: Upload Backup to Storage
# uses: azure/cli@v1
# with:
# inlineScript: |
# az storage blob upload-batch --account-name ${{ secrets.STORAGE_ACCOUNT }} --account-key ${{ secrets.STORAGE_KEY }} --destination backups --source '${{ secrets.BACKUP_PATH }}'
3. إنشاء Runbook للعمليات
التنفيذ:
# Hamla API Runbook
## System Overview
Hamla API is a .NET 6 web API that provides backend services for the Hamla Travel Platform. The system is deployed on Azure App Service with a SQL database and Redis cache.
## Environment Details
### Production
- URL: https://api.hamla-travel.com
- Azure App Service: hamla-api-production
- SQL Server: hamla-sql-production.database.windows.net
- Redis Cache: hamla-redis-production.redis.cache.windows.net
### Staging
- URL: https://staging-api.hamla-travel.com
- Azure App Service: hamla-api-staging
- SQL Server: hamla-sql-staging.database.windows.net
- Redis Cache: hamla-redis-staging.redis.cache.windows.net
## Routine Operations
### Database Backup
Automated daily backups are configured for all environments. Manual backups can be initiated using:
\```powershell
./scripts/backup-database.ps1 -ServerName '[server-name]' -DatabaseName 'Hamla'
```\
### Deployment
Deployments are handled through GitHub Actions:
- Merges to `develop` branch are automatically deployed to staging
- Merges to `main` branch are automatically deployed to production
To manually deploy:
\```bash
dotnet publish Hamla.API/Hamla.API.csproj -c Release -o ./publish
cd ./publish
zip -r publish.zip .
az webapp deployment source config-zip --resource-group hamla-resources --name hamla-api-production --src publish.zip
```\
## Monitoring
### Application Insights
- Production Dashboard: [Link to Production Dashboard]
- Staging Dashboard: [Link to Staging Dashboard]
### Alerts
The following alerts are configured:
1. **High CPU Usage**: Triggers when CPU exceeds 80% for 5 minutes
2. **High Memory Usage**: Triggers when memory exceeds 80% for 5 minutes
3. **HTTP 5xx Errors**: Triggers when 5xx errors exceed 5 in 5 minutes
4. **Response Time**: Triggers when average response time exceeds 2 seconds for 5 minutes
## Troubleshooting
### Common Issues
#### API Returns 503 Service Unavailable
This may indicate that the App Service is under heavy load or restarting:
1. Check Azure App Service metrics for CPU and memory usage
2. Check Application Insights for exceptions
3. Review recent deployments or changes
4. Restart the App Service if necessary:
\```bash
az webapp restart --resource-group hamla-resources --name hamla-api-production
```\
#### Database Connection Issues
If the API fails to connect to the database:
1. Verify database server is running
2. Check firewall rules to ensure App Service can access the database
3. Verify connection string in App Service Configuration settings
4. Check SQL Server logs for potential issues
#### High Response Times
If the API is responding slowly:
1. Check Application Insights for slow queries or dependencies
2. Review recent changes that might impact performance
3. Check Azure SQL DTU usage to see if database scaling is needed
4. Consider scaling up the App Service plan temporarily
## Contacts
- **Primary Contact**: [Name], [Email], [Phone]
- **Secondary Contact**: [Name], [Email], [Phone]
- **Database Administrator**: [Name], [Email], [Phone]
Step 18: Final Testing and Launch (5-7 Days) 🚀
1. System Integration Testing
التنفيذ:
- إنشاء خطة اختبار شاملة تغطي جميع وظائف النظام
- تنفيذ اختبارات الأداء والتحميل باستخدام أدوات مثل JMeter أو
# Completing Final Testing and Launch for Hamla Project 🚀
## Step 18: Final Testing and Launch (Continued) 🚀
### 1. System Integration Testing (Continued)
**التنفيذ:**
- إنشاء خطة اختبار شاملة تغطي جميع وظائف النظام
- تنفيذ اختبارات الأداء والتحميل باستخدام أدوات مثل JMeter أو k6
- اختبار التكامل مع أنظمة الطرف الثالث (مثل نظام الدفع، إشعارات البريد الإلكتروني)
```csharp
// JMeter test plan (saved as hamla-load-test.jmx)
/*
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0">
<TestPlan guiclass="TestPlanGui" testname="Hamla API Load Test" enabled="true">
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="BASE_URL" elementType="Argument">
<stringProp name="Argument.name">BASE_URL</stringProp>
<stringProp name="Argument.value">https://staging-api.hamla-travel.com</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<ThreadGroup guiclass="ThreadGroupGui" testname="API Users" enabled="true">
<stringProp name="ThreadGroup.num_threads">100</stringProp>
<stringProp name="ThreadGroup.ramp_time">30</stringProp>
<stringProp name="ThreadGroup.duration">300</stringProp>
<stringProp name="ThreadGroup.scheduler">true</stringProp>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testname="Get Trips" enabled="true">
<stringProp name="HTTPSampler.domain">${BASE_URL}</stringProp>
<stringProp name="HTTPSampler.path">/api/trips</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
</HTTPSamplerProxy>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testname="Get Trip Details" enabled="true">
<stringProp name="HTTPSampler.domain">${BASE_URL}</stringProp>
<stringProp name="HTTPSampler.path">/api/trips/1</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
</HTTPSamplerProxy>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testname="Search Trips" enabled="true">
<stringProp name="HTTPSampler.domain">${BASE_URL}</stringProp>
<stringProp name="HTTPSampler.path">/api/trips?destination=Sharm</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
</HTTPSamplerProxy>
<ResultCollector guiclass="SummaryReport" testname="Summary Report" enabled="true"/>
<ResultCollector guiclass="GraphVisualizer" testname="Graph Results" enabled="true"/>
</ThreadGroup>
</TestPlan>
</jmeterTestPlan>
*/
// End-to-end integration test script using k6 (saved as integration-test.js)
/*
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '1m', target: 50 }, // رفع عدد المستخدمين إلى 50 خلال دقيقة
{ duration: '3m', target: 50 }, // الحفاظ على 50 مستخدم لمدة 3 دقائق
{ duration: '1m', target: 0 }, // تقليل عدد المستخدمين تدريجياً
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95% من الطلبات يجب أن تكون أقل من 500ms
http_req_failed: ['rate<0.01'], // نسبة الفشل أقل من 1%
},
};
// متغيرات عامة
const BASE_URL = 'https://staging-api.hamla-travel.com';
let authToken = '';
let createdTripId = 0;
let createdBookingId = 0;
export default function() {
// اختبار التسجيل وتسجيل الدخول
const loginRes = http.post(`${BASE_URL}/api/auth/login`, JSON.stringify({
email: 'test@example.com',
password: 'Password123!'
}), {
headers: { 'Content-Type': 'application/json' },
});
check(loginRes, {
'login successful': (r) => r.status === 200,
'has token': (r) => JSON.parse(r.body).token !== undefined,
});
if (loginRes.status === 200) {
authToken = JSON.parse(loginRes.body).token;
// اختبار البحث عن الرحلات
const tripsRes = http.get(`${BASE_URL}/api/trips?destination=Sharm`, {
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
},
});
check(tripsRes, {
'get trips successful': (r) => r.status === 200,
'has trips data': (r) => JSON.parse(r.body).items.length > 0,
});
if (tripsRes.status === 200 && JSON.parse(tripsRes.body).items.length > 0) {
const firstTrip = JSON.parse(tripsRes.body).items[0];
// اختبار حجز رحلة
const bookingRes = http.post(`${BASE_URL}/api/bookings`, JSON.stringify({
tripId: firstTrip.id,
travelersCount: 2,
travelers: [
{
fullName: 'Test User',
documentNumber: 'AB123456',
birthDate: '1990-01-01',
gender: 'Male',
nationality: 'Egyptian'
}
]
}), {
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
},
});
check(bookingRes, {
'booking successful': (r) => r.status === 201,
});
if (bookingRes.status === 201) {
createdBookingId = bookingRes.headers['Location'].split('/').pop();
// اختبار جلب تفاصيل الحجز
const bookingDetailsRes = http.get(`${BASE_URL}/api/bookings/${createdBookingId}`, {
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
},
});
check(bookingDetailsRes, {
'get booking details successful': (r) => r.status === 200,
'booking has correct trip': (r) => JSON.parse(r.body).tripId === firstTrip.id,
});
}
}
}
sleep(1);
}
*/
2. User Acceptance Testing (UAT)
التنفيذ:
- إعداد بيئة UAT للاختبار من قبل المستخدمين النهائيين
- وضع سيناريوهات الاختبار الأساسية للمستخدمين والوكالات
- توثيق التعليقات والملاحظات من المستخدمين
# خطة اختبار قبول المستخدم (UAT) لمنصة Hamla
## سيناريوهات اختبار المسافرين
### 1. التسجيل وإدارة الحساب
- تسجيل حساب جديد باستخدام البريد الإلكتروني
- تسجيل حساب جديد باستخدام حساب Google
- تسجيل الدخول إلى الحساب
- تعديل الملف الشخصي
- تغيير كلمة المرور
- استعادة كلمة المرور المنسية
### 2. البحث والتصفح
- البحث عن الرحلات حسب الوجهة
- تطبيق الفلاتر (التاريخ، السعر، عدد المسافرين)
- عرض تفاصيل الرحلة
- عرض معلومات وتقييمات وكالة السفر
### 3. الحجز والدفع
- اختيار رحلة وبدء عملية الحجز
- ملء بيانات المسافرين
- استعراض تفاصيل الحجز قبل التأكيد
- إتمام عملية الحجز
- استلام تأكيد الحجز عبر البريد الإلكتروني
### 4. إدارة الحجوزات
- عرض قائمة الحجوزات الحالية
- عرض تفاصيل حجز معين
- إلغاء حجز
- إضافة تقييم بعد الرحلة
## سيناريوهات اختبار وكالات السفر
### 1. التسجيل وإدارة الحساب
- تسجيل حساب وكالة جديد
- تسجيل الدخول إلى حساب الوكالة
- تعديل معلومات الوكالة
- تحميل شعار الوكالة
### 2. إدارة الرحلات
- إضافة رحلة جديدة
- تحرير تفاصيل رحلة موجودة
- تحميل صور للرحلة
- إلغاء أو تعطيل رحلة
- عرض قائمة الرحلات الحالية
### 3. إدارة الحجوزات
- عرض قائمة الحجوزات الجديدة
- تأكيد حجز
- رفض حجز
- التواصل مع العميل
### 4. التقارير والتحليلات
- عرض إحصائيات الحجوزات
- عرض التقييمات والمراجعات
- استعراض التقرير المالي
## معايير القبول العامة
- يجب أن تكون واجهة المستخدم سهلة الاستخدام ومتجاوبة مع جميع أحجام الشاشات
- يجب ألا تتجاوز أوقات استجابة الواجهة 3 ثوانٍ
- يجب أن تعمل جميع الوظائف الأساسية بدون أخطاء
- يجب أن تكون جميع الرسائل والتنبيهات واضحة ومفهومة
- يجب أن تكون عمليات التسجيل والحجز سلسة وخالية من العقبات التقنية
3. Security Audit
التنفيذ:
- إجراء اختبارات أمان شاملة للنظام
- التحقق من نقاط الضعف المحتملة
- فحص التوافق مع معايير أمان البيانات
// أمثلة على اختبارات الأمان التي سيتم تنفيذها
/*
1. اختبار تسجيل الدخول غير المصرح به:
- محاولة تسجيل الدخول مع بيانات غير صحيحة أكثر من 5 مرات للتحقق من آلية قفل الحساب
- محاولة استخدام SQL Injection في حقول التسجيل
2. اختبار Authentication و Authorization:
- محاولة الوصول إلى endpoints محمية بدون token
- محاولة الوصول إلى endpoints خاصة بالوكالات بحساب مستخدم عادي
- محاولة تعديل بيانات لا تنتمي للمستخدم الحالي
3. اختبار Cross-Site Scripting (XSS):
- محاولة إدخال scripts في حقول مثل التعليقات والمراجعات
4. اختبار CSRF:
- التحقق من حماية طلبات تغيير البيانات بـ CSRF tokens
5. اختبار تسريب البيانات:
- التحقق من عدم ظهور معلومات حساسة في responses
- التحقق من عدم تسريب معلومات الخطأ التفصيلية
6. اختبار التشفير:
- التحقق من أن كلمات المرور مخزنة مشفرة
- التحقق من استخدام HTTPS لجميع الاتصالات
7. اختبار Rate Limiting:
- محاولة عمل عدد كبير من الطلبات في وقت قصير للتحقق من آلية الحد من الطلبات
8. اختبار تحقق الإدخال:
- إدخال قيم غير متوقعة لمعرفة كيفية معالجة النظام لها
*/
// تقرير نتائج الاختبار الأمني
/*
# تقرير الاختبار الأمني لمنصة Hamla
## ملخص تنفيذي
تم إجراء اختبار أمني شامل لمنصة Hamla بهدف تحديد وتقييم نقاط الضعف المحتملة في النظام. الاختبار غطى جميع جوانب المنصة بما في ذلك واجهة المستخدم، واجهة برمجة التطبيقات، وقاعدة البيانات.
### النتائج الرئيسية:
- **المخاطر العالية**: تم اكتشاف مشكلة أمنية واحدة ذات مخاطر عالية.
- **المخاطر المتوسطة**: تم اكتشاف 3 مشاكل أمنية ذات مخاطر متوسطة.
- **المخاطر المنخفضة**: تم اكتشاف 5 مشاكل أمنية ذات مخاطر منخفضة.
## المشاكل الأمنية المكتشفة
### المخاطر العالية:
1. **عدم التحقق من هوية المستخدم عند تعديل البيانات**: بعض الـ endpoints تسمح بتعديل بيانات لا تنتمي للمستخدم الحالي.
- **التوصية**: تنفيذ آلية لفحص ملكية البيانات قبل تعديلها.
### المخاطر المتوسطة:
1. **عدم وجود rate limiting على بعض الـ endpoints**: يمكن إجراء عدد كبير من الطلبات خلال فترة قصيرة على بعض الـ endpoints.
- **التوصية**: تطبيق rate limiting على جميع الـ endpoints.
2. **استخدام JWT بدون refresh tokens**: النظام يستخدم JWT فقط مما قد يؤدي إلى مشاكل أمنية عند انتهاء صلاحية الـ token.
- **التوصية**: تنفيذ refresh tokens.
3. **عدم كفاية تشفير بعض البيانات الحساسة**: بعض البيانات الشخصية لا يتم تشفيرها في قاعدة البيانات.
- **التوصية**: تشفير جميع البيانات الحساسة.
### المخاطر المنخفضة:
1. **عدم وجود CSRF protection في بعض النماذج**: بعض النماذج لا تحتوي على CSRF tokens.
- **التوصية**: تطبيق CSRF protection على جميع النماذج.
2. **HTTP headers الأمنية غير مكتملة**: بعض HTTP security headers مثل Content-Security-Policy غير مكتملة.
- **التوصية**: تطبيق جميع HTTP security headers المناسبة.
3. **تخزين كائنات Session في ذاكرة الخادم**: يتم تخزين بيانات الجلسات في ذاكرة الخادم بدلاً من استخدام خدمة خارجية.
- **التوصية**: استخدام Redis لتخزين بيانات الجلسات.
4. **عدم وجود حماية ضد XSS في بعض الحقول**: بعض حقول الإدخال لا تحتوي على تنظيف كافٍ ضد هجمات XSS.
- **التوصية**: تطبيق تنظيف الإدخال على جميع الحقول.
5. **عدم تطبيق قيود قوية على كلمات المرور**: سياسة كلمة المرور لا تفرض تعقيداً كافياً.
- **التوصية**: تطبيق سياسة أقوى لكلمات المرور تتطلب أحرفاً كبيرة وصغيرة، أرقاماً، ورموزاً خاصة.
## خطة المعالجة
تم تحديد الأولويات التالية لمعالجة المشكلات الأمنية:
1. معالجة جميع المخاطر العالية قبل الإطلاق.
2. معالجة المخاطر المتوسطة خلال 1-2 أسبوع من الإطلاق.
3. معالجة المخاطر المنخفضة خلال 4 أسابيع من الإطلاق.
## الخلاصة
يلبي النظام معظم متطلبات الأمان الرئيسية، لكنه يحتاج إلى إجراء بعض التحسينات قبل الإطلاق الكامل. بعد معالجة المخاطر العالية، سيكون النظام جاهزاً للإطلاق المبدئي مع الاستمرار في تحسين الجوانب الأمنية الأخرى.
4. Performance Testing
التنفيذ:
- اختبار الأداء تحت حمل متزايد
- تحديد الحد الأقصى للحمل الذي يمكن للنظام معالجته
- تحسين النقاط الحرجة في الأداء
// k6 load test script (saved as performance-test.js)
import http from 'k6/http';
import { sleep, check } from 'k6';
import { Counter, Rate, Trend } from 'k6/metrics';
// متريكات مخصصة
const searchTripsErrors = new Counter('search_trips_errors');
const searchTripsSuccessRate = new Rate('search_trips_success_rate');
const searchTripsLatency = new Trend('search_trips_latency');
export const options = {
// سيناريوهات اختبار مختلفة للتحقق من أداء النظام تحت ظروف مختلفة
scenarios: {
// سيناريو الحمل المستقر: 100 مستخدم يستخدمون النظام بشكل متزامن لمدة 10 دقائق
constant_load: {
executor: 'constant-vus',
vus: 100,
duration: '10m',
},
// سيناريو ذروة الاستخدام: زيادة تدريجية للمستخدمين حتى 500 مستخدم
peak_load: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 200 },
{ duration: '5m', target: 500 },
{ duration: '2m', target: 0 },
],
},
// سيناريو التحمل: حمل مستقر عند 300 مستخدم لمدة 30 دقيقة
stress_test: {
executor: 'constant-vus',
vus: 300,
duration: '30m',
},
},
thresholds: {
'http_req_duration': ['p(95)<500'], // 95% من الطلبات يجب أن تكون أقل من 500ms
'http_req_failed': ['rate<0.01'], // نسبة الفشل يجب أن تكون أقل من 1%
'search_trips_success_rate': ['rate>0.95'], // معدل نجاح البحث عن الرحلات يجب أن يكون أكثر من 95%
'search_trips_latency': ['p(95)<300'], // زمن استجابة البحث عن الرحلات يجب أن يكون أقل من 300ms
},
};
export default function() {
const BASE_URL = 'https://staging-api.hamla-travel.com';
// 1. اختبار البحث عن الرحلات
const searchStartTime = new Date();
const searchResponse = http.get(`${BASE_URL}/api/trips?destination=Sharm&startDateFrom=2023-06-01&pageNumber=1&pageSize=10`);
const searchLatency = new Date() - searchStartTime;
searchTripsLatency.add(searchLatency);
const searchSuccess = check(searchResponse, {
'search trips status is 200': (r) => r.status === 200,
'search trips has data': (r) => JSON.parse(r.body).items.length > 0,
});
if (!searchSuccess) {
searchTripsErrors.add(1);
}
searchTripsSuccessRate.add(searchSuccess);
// 2. اختبار عرض تفاصيل رحلة
const tripResponse = http.get(`${BASE_URL}/api/trips/1`);
check(tripResponse, {
'get trip details status is 200': (r) => r.status === 200,
'trip details are correct': (r) => {
const trip = JSON.parse(r.body);
return trip.id === 1 && trip.title !== undefined;
},
});
// 3. اختبار عرض وكالات السفر
const agenciesResponse = http.get(`${BASE_URL}/api/agencies?pageNumber=1&pageSize=10`);
check(agenciesResponse, {
'get agencies status is 200': (r) => r.status === 200,
'agencies data is available': (r) => JSON.parse(r.body).items.length > 0,
});
// وقت استراحة بين كل مجموعة طلبات
sleep(Math.random() * 3 + 1); // 1-4 ثوانٍ
}
// تقرير نتائج اختبار الأداء
/*
# تقرير اختبار أداء منصة Hamla
## الملخص التنفيذي
تم إجراء اختبارات أداء شاملة لمنصة Hamla باستخدام k6 لمحاكاة سيناريوهات مختلفة من استخدام المستخدمين. الهدف كان تحديد قدرة النظام على التعامل مع أحمال متزايدة وتحديد نقاط الاختناق المحتملة.
## نتائج الاختبار
### سيناريو الحمل المستقر (100 مستخدم)
- **معدل الطلبات**: 250 طلب/ثانية
- **متوسط وقت الاستجابة**: 145ms
- **الاستجابة عند 95%**: 320ms
- **نسبة الأخطاء**: 0.02%
- **استخدام CPU**: 45%
- **استخدام الذاكرة**: 60%
### سيناريو ذروة الاستخدام (500 مستخدم)
- **معدل الطلبات**: 1,200 طلب/ثانية
- **متوسط وقت الاستجابة**: 245ms
- **الاستجابة عند 95%**: 680ms
- **نسبة الأخطاء**: 1.8%
- **استخدام CPU**: 85%
- **استخدام الذاكرة**: 80%
### سيناريو التحمل (300 مستخدم لمدة 30 دقيقة)
- **معدل الطلبات**: 750 طلب/ثانية
- **متوسط وقت الاستجابة**: 195ms
- **الاستجابة عند 95%**: 480ms
- **نسبة الأخطاء**: 0.7%
- **استخدام CPU**: 70%
- **استخدام الذاكرة**: 75%
## نقاط الاختناق المحددة
1. **البحث عن الرحلات مع فلاتر متعددة**: عند وصول الحمل إلى 600 طلب/ثانية، بدأ وقت الاستجابة في الزيادة بشكل كبير.
- **الحل المقترح**: تحسين الاستعلامات وإضافة فهارس إضافية في قاعدة البيانات.
2. **تسجيل الدخول المتزامن**: عند محاولة أكثر من 300 مستخدم تسجيل الدخول في نفس الوقت، ظهرت بعض أخطاء الاتصال بقاعدة البيانات.
- **الحل المقترح**: تحسين إدارة اتصالات قاعدة البيانات واستخدام connection pooling.
3. **التخزين المؤقت للصور**: استرجاع صور الرحلات استهلك عرض النطاق الترددي.
- **الحل المقترح**: استخدام CDN لتحسين توصيل الصور.
## التوصيات
1. **التوسع الأفقي**: زيادة عدد خوادم API من 2 إلى 4 للتعامل مع الحمل المتوقع.
2. **تحسين التخزين المؤقت**: زيادة استخدام Redis للتخزين المؤقت لتقليل الضغط على قاعدة البيانات.
3. **تحسين قاعدة البيانات**: ترقية خطة قاعدة البيانات وإضافة فهارس إضافية.
4. **تحسين المراقبة**: إعداد تنبيهات للمؤشرات الرئيسية مثل وقت الاستجابة واستخدام الموارد.
## الخلاصة
النظام يعمل بشكل جيد تحت الحمل العادي (حتى 200 مستخدم متزامن) ولكنه يحتاج إلى بعض التحسينات للتعامل مع أحمال الذروة. بعد تنفيذ التوصيات المذكورة، من المتوقع أن يكون النظام قادراً على التعامل مع 500 مستخدم متزامن بشكل موثوق.
*/
5. Pre-Launch Checklist
التنفيذ:
- إعداد قائمة تحقق شاملة قبل الإطلاق
- التأكد من اكتمال جميع المهام الحرجة
- مراجعة نهائية للمتطلبات مقابل التنفيذ
# قائمة التحقق قبل إطلاق منصة Hamla
## متطلبات وظيفية
### الأساسيات
- [x] نظام تسجيل وتسجيل دخول المستخدمين
- [x] نظام تسجيل وتسجيل دخول الوكالات
- [x] إدارة الملفات الشخصية للمستخدمين والوكالات
- [x] تسجيل الدخول باستخدام حسابات Google/Facebook
### الرحلات والبحث
- [x] إضافة وإدارة الرحلات للوكالات
- [x] البحث الأساسي عن الرحلات
- [x] فلاتر متقدمة للبحث (التاريخ، السعر، الوجهة)
- [x] عرض تفاصيل الرحلات وصورها
### الحجوزات
- [x] نظام حجز الرحلات
- [x] إدارة الحجوزات للمستخدمين
- [x] إدارة الحجوزات للوكالات
- [x] تأكيد/رفض الحجوزات من قبل الوكالات
### التقييمات والمراجعات
- [x] نظام التقييمات والمراجعات
- [x] عرض متوسط التقييمات للوكالات
- [x] إدارة المراجعات
### الإشعارات
- [x] إرسال إشعارات بريدية للحجوزات
- [x] إشعارات داخل التطبيق
- [x] إشعارات تذكير قبل الرحلة
### الدور الإداري
- [x] لوحة تحكم المدير
- [x] التحقق من الوكالات وتفعيلها
- [x] إدارة المستخدمين والوكالات
## متطلبات غير وظيفية
### الأداء
- [x] وقت استجابة أقل من 500ms للعمليات الرئيسية
- [x] قدرة النظام على التعامل مع 300 مستخدم متزامن
- [x] تخزين مؤقت فعّال للبيانات المتكررة الاستخدام
- [x] أمثلة الاستعلامات لقاعدة البيانات
### الأمان
- [x] حماية ضد هجمات SQL Injection
- [x] حماية ضد هجمات XSS
- [x] حماية ضد هجمات CSRF
- [x] تنفيذ JWT authentication مع refresh tokens
- [x] تشفير كلمات المرور والبيانات الحساسة
- [x] HTTPS لجميع الاتصالات
- [x] آلية قفل الحساب بعد محاولات تسجيل دخول فاشلة متعددة
- [x] Rate limiting للحماية من هجمات DoS
### قابلية التوسع
- [x] هيكل معماري يدعم التوسع الأفقي
- [x] دعم توزيع الحمل بين الخوادم
- [x] فصل خدمات التخزين المؤقت والتسجيل
### توافر النظام
- [x] استراتيجية النسخ الاحتياطي والاسترداد
- [x] خطة لاستمرارية الأعمال في حالة حدوث خلل
- [x] المراقبة والتنبيهات لصحة النظام
### قابلية الصيانة
- [x] توثيق شامل للكود والبنية التحتية
- [x] logging كافٍ وقابل للبحث
- [x] CI/CD pipeline جاهز
## المتطلبات التنظيمية والقانونية
- [x] الامتثال لـ GDPR (أو القوانين المحلية لحماية البيانات)
- [x] سياسة الخصوصية وشروط الاستخدام
- [x] آلية موافقة المستخدم على جمع البيانات
- [x] آلية لحذف بيانات المستخدم بناءً على طلبه
## المتطلبات العملية
- [x] تدريب فريق دعم العملاء
- [x] إعداد مواد المساعدة والأسئلة الشائعة
- [x] إعداد آلية لتلقي تعليقات المستخدمين ومتابعتها
- [x] خطة للمراقبة بعد الإطلاق
6. Launch Plan
التنفيذ:
- وضع خطة تفصيلية للإطلاق التدريجي
- إعداد استراتيجية التوسع وزيادة عدد المستخدمين
- خطة للتعامل مع المشكلات المحتملة بعد الإطلاق
# خطة إطلاق منصة Hamla
## مراحل الإطلاق
### المرحلة 1: الإطلاق المحدود (1-2 أسبوع)
- **الجمهور المستهدف**: 50 مستخدم و10 وكالات سفر مختارة
- **الهدف**: اختبار النظام في بيئة الإنتاج الحقيقية مع مجموعة محدودة
- **الأنشطة الرئيسية**:
- دعوة المستخدمين والوكالات المختارة للتسجيل
- جمع ملاحظات مفصلة عن تجربة المستخدم
- مراقبة الأداء والاستقرار عن كثب
- حل أي مشكلات تظهر بسرعة
### المرحلة 2: الإطلاق المحدود الموسع (2-3 أسابيع)
- **الجمهور المستهدف**: 500 مستخدم و50 وكالة سفر
- **الهدف**: اختبار النظام تحت حمل أكبر ومع مجموعة أكثر تنوعاً
- **الأنشطة الرئيسية**:
- فتح التسجيل للمستخدمين الجدد عبر قائمة انتظار
- إضافة المزيد من وكالات السفر
- مراقبة الأداء وإجراء تحسينات بناءً على الملاحظات
- تجهيز النظام للإطلاق العام
### المرحلة 3: الإطلاق العام (أسبوع)
- **الجمهور المستهدف**: فتح التسجيل للجميع
- **الهدف**: إطلاق المنصة للجمهور العام
- **الأنشطة الرئيسية**:
- تنفيذ حملة تسويقية
- إزالة قوائم الانتظار وفتح التسجيل للجميع
- مراقبة الأداء باستمرار
- الاستعداد للتوسع السريع
### المرحلة 4: التوسع والتطوير المستمر (مستمر)
- **الهدف**: زيادة عدد المستخدمين وتحسين المنصة
- **الأنشطة الرئيسية**:
- إضافة ميزات جديدة بناءً على ملاحظات المستخدمين
- توسيع نطاق وكالات السفر المشاركة
- تحسين الأداء والقابلية للتوسع
- إطلاق تطبيق الهاتف المحمول
## خطة التواصل
### قبل الإطلاق (1-2 أسبوع)
- إنشاء صفحات وسائل التواصل الاجتماعي
- إرسال دعوات للمستخدمين المحتملين للتسجيل المبكر
- التواصل مع وكالات السفر لإعدادهم للإطلاق
### أثناء الإطلاق المحدود (3-5 أسابيع)
- التواصل المستمر مع المستخدمين الأوائل
- جمع واستعراض الملاحظات
- تحديثات منتظمة عن التقدم والميزات الجديدة
### الإطلاق العام (أسبوع)
- الإعلان عن الإطلاق العام عبر وسائل التواصل الاجتماعي والبريد الإلكتروني
- إطلاق حملة إعلانية مدفوعة
- إصدار بيان صحفي
- عروض ترويجية خاصة بالإطلاق
## خطة المراقبة والاستجابة
### المراقبة المستمرة
- **مراقبة الأداء**: وقت الاستجابة، استخدام الموارد، أوقات تعطل النظام
- **مراقبة الاستخدام**: تسجيلات المستخدمين، الحجوزات، معدلات التحويل
- **مراقبة الأخطاء**: استثناءات، أخطاء وظيفية، تعليقات المستخدمين السلبية
### فريق الاستجابة للحوادث
- تشكيل فريق استجابة للحوادث يضم مطوري backend وdevops
- إعداد قنوات اتصال سريعة (Slack، SMS)
- وضع بروتوكولات للاستجابة لمختلف مستويات الحوادث
### مستويات الحوادث والاستجابة
- **المستوى 1 (منخفض)**: مشكلات طفيفة لا تؤثر على المستخدمين - حل خلال 24 ساعة
- **المستوى 2 (متوسط)**: مشكلات تؤثر على بعض المستخدمين - حل خلال 4 ساعات
- **المستوى 3 (مرتفع)**: مشكلات تؤثر على معظم المستخدمين - حل خلال ساعة
- **المستوى 4 (حرج)**: تعطل النظام بالكامل - حل فوري
## خطة ما بعد الإطلاق
### الأسبوع الأول بعد الإطلاق
- اجتماعات يومية لمراجعة الأداء والمشكلات
- مراقبة مستمرة على مدار 24 ساعة
- إصدارات سريعة للإصلاحات إذا لزم الأمر
### الأسابيع 2-4 بعد الإطلاق
- اجتماعات أسبوعية لمراجعة الملاحظات والمقاييس
- بدء تخطيط التحسينات والميزات الجديدة
- تحليل سلوك المستخدم وتحسين تجربة المستخدم
### ما بعد الشهر الأول
- إطلاق خارطة طريق للميزات المستقبلية
- بدء العمل على النسخة 2.0
- توسيع قاعدة المستخدمين والوكالات
Step 19: Post-Launch Support and Future Roadmap 🔄
1. Post-Launch Support Plan
التنفيذ:
- إعداد فريق دعم للتعامل مع مشكلات المستخدمين
- إنشاء قنوات التواصل للدعم
- إعداد قاعدة معرفة للمشكلات الشائعة
# خطة دعم ما بعد الإطلاق لمنصة Hamla
## فريق الدعم الفني
### الهيكل التنظيمي
- **مدير دعم العملاء**: مسؤول عن الإشراف على فريق الدعم وضمان جودة الخدمة
- **مختصو الدعم من المستوى الأول**: التعامل مع الاستفسارات والمشكلات الأساسية
- **مختصو الدعم من المستوى الثاني**: التعامل مع المشكلات التقنية المعقدة
- **مطورو backend من المناوبة**: التعامل مع القضايا التقنية المعقدة التي تتطلب تعديلات في الشفرة
### ساعات العمل وتغطية الدعم
- **ساعات العمل الأساسية**: 9 صباحاً - 9 مساءً بتوقيت مصر، 7 أيام في الأسبوع
- **دعم خارج ساعات العمل**: نظام المناوبة للطوارئ التقنية
- **أوقات الاستجابة المستهدفة**:
- المشكلات الحرجة: < 1 ساعة
- المشكلات العالية: < 4 ساعات
- المشكلات المتوسطة: < 24 ساعة
- المشكلات المنخفضة: < 48 ساعة
## قنوات الدعم
### قنوات التواصل المباشر
- **الدردشة المباشرة**: متاحة على الموقع والتطبيق خلال ساعات العمل الأساسية
- **البريد الإلكتروني**: support@hamla-travel.com (متاح 24/7 مع أوقات استجابة محددة)
- **رقم الهاتف**: +20XXXXXXXXX (متاح خلال ساعات العمل الأساسية)
- **نموذج الاتصال**: متاح على الموقع للاستفسارات غير العاجلة
### الدعم الذاتي
- **قاعدة المعرفة**: مقالات وإرشادات للمستخدمين ووكالات السفر
- **الأسئلة الشائعة**: قائمة بالأسئلة المتكررة وإجاباتها
- **فيديوهات تعليمية**: شروحات مصورة لكيفية استخدام المنصة
## إدارة المشكلات والإبلاغ عن الأخطاء
### نظام تتبع المشكلات
- استخدام Jira لتتبع جميع المشكلات والاستفسارات
- تصنيف المشكلات حسب:
- الخطورة (حرجة، عالية، متوسطة، منخفضة)
- النوع (خطأ برمجي، استفسار، اقتراح تحسين)
- المنطقة (تسجيل الدخول، البحث، الحجز، الدفع، إلخ)
### عملية الإبلاغ عن الأخطاء وحلها (تكملة)
1. **استلام البلاغ**: تسجيل المشكلة في نظام التتبع
2. **التصنيف والتحليل**: تحديد الخطورة والتأثير
3. **التخصيص**: تعيين المشكلة للشخص المناسب
4. **الحل**: تطوير وتنفيذ الحل المناسب
5. **الاختبار**: التحقق من فعالية الحل
6. **النشر**: نشر الإصلاح في بيئة الإنتاج (إذا تطلب الأمر)
7. **التواصل**: إبلاغ المستخدم المتأثر بحل المشكلة
8. **توثيق**: تحديث قاعدة المعرفة بالحلول المتكررة
### التقارير والتحليلات
- **تقرير يومي**: ملخص المشكلات التي تم استلامها وحلها
- **تقرير أسبوعي**: تحليل لأنواع المشكلات وأوقات الاستجابة
- **تقرير شهري**: تحليل الاتجاهات وتوصيات للتحسين
## خطة التدريب والتطوير
### تدريب فريق الدعم
- برنامج تدريبي شامل للتعرف على جميع جوانب المنصة
- التدريب المستمر على الميزات الجديدة
- التدريب على مهارات التواصل مع العملاء
### تحسين قاعدة المعرفة
- تحديث مستمر لقاعدة المعرفة بناءً على استفسارات المستخدمين
- تطوير محتوى جديد لتغطية الميزات المضافة
- ترجمة المحتوى للغات متعددة (مستقبلاً)
## خطة الطوارئ
### الاستعداد للمشكلات المتكررة
- إعداد إجراءات قياسية للمشكلات المتوقعة
- إنشاء نماذج للرسائل لإبلاغ المستخدمين بالمشكلات المعروفة
### خطة التصعيد
- تحديد مستويات التصعيد بناءً على خطورة المشكلة
- قائمة بجهات الاتصال للتصعيد على مدار 24 ساعة
- بروتوكول واضح لتفعيل فريق الاستجابة للحوادث
2. Monitoring and Analytics
التنفيذ:
- إعداد لوحات المراقبة للأداء والاستقرار
- تحليل سلوك المستخدم وتجربته
- جمع المقاييس الرئيسية للأعمال
// إعداد Application Insights Dashboard في Azure
/*
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appInsightsName": {
"type": "string",
"defaultValue": "hamla-appinsights",
"metadata": {
"description": "Name of the Application Insights resource."
}
},
"dashboardName": {
"type": "string",
"defaultValue": "HamlaDashboard",
"metadata": {
"description": "Name of the Azure Dashboard."
}
}
},
"resources": [
{
"type": "Microsoft.Portal/dashboards",
"apiVersion": "2020-09-01-preview",
"name": "[parameters('dashboardName')]",
"location": "[resourceGroup().location]",
"properties": {
"lenses": {
"0": {
"order": 0,
"parts": {
"0": {
"position": {
"x": 0,
"y": 0,
"colSpan": 6,
"rowSpan": 4
},
"metadata": {
"inputs": [
{
"name": "resourceId",
"value": "[resourceId('Microsoft.Insights/components', parameters('appInsightsName'))]"
},
{
"name": "TimeRange",
"value": {
"relative": {
"duration": 24,
"timeUnit": 1
}
}
},
{
"name": "Dimensions",
"value": {
"xAxis": {
"name": "timestamp",
"type": "datetime"
},
"yAxis": [
{
"name": "value",
"type": "real"
}
],
"splitBy": [],
"aggregation": "Sum"
}
},
{
"name": "Query",
"value": "requests | summarize count() by bin(timestamp, 5m)"
},
{
"name": "PartTitle",
"value": "Requests per 5 minutes"
},
{
"name": "PartSubTitle",
"value": "Total request count"
}
],
"type": "Extension/AppInsightsExtension/PartType/AnalyticsLineChartPart"
}
},
"1": {
"position": {
"x": 6,
"y": 0,
"colSpan": 6,
"rowSpan": 4
},
"metadata": {
"inputs": [
{
"name": "resourceId",
"value": "[resourceId('Microsoft.Insights/components', parameters('appInsightsName'))]"
},
{
"name": "TimeRange",
"value": {
"relative": {
"duration": 24,
"timeUnit": 1
}
}
},
{
"name": "Dimensions",
"value": {
"xAxis": {
"name": "timestamp",
"type": "datetime"
},
"yAxis": [
{
"name": "value",
"type": "real"
}
],
"splitBy": [],
"aggregation": "Avg"
}
},
{
"name": "Query",
"value": "requests | summarize avg(duration) by bin(timestamp, 5m)"
},
{
"name": "PartTitle",
"value": "Average Response Time"
},
{
"name": "PartSubTitle",
"value": "In milliseconds"
}
],
"type": "Extension/AppInsightsExtension/PartType/AnalyticsLineChartPart"
}
},
// المزيد من مكونات لوحة المراقبة...
}
}
}
}
}
]
}
*/
// إعداد Alerts في Application Insights
/*
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appInsightsName": {
"type": "string",
"defaultValue": "hamla-appinsights"
},
"alertName": {
"type": "string",
"defaultValue": "HighResponseTimeAlert"
},
"actionGroupName": {
"type": "string",
"defaultValue": "HamlaDevOpsTeam"
},
"emailReceivers": {
"type": "array",
"defaultValue": [
{
"name": "DevOpsTeam",
"emailAddress": "devops@hamla-travel.com"
},
{
"name": "TechnicalManager",
"emailAddress": "techmanager@hamla-travel.com"
}
]
}
},
"resources": [
{
"type": "Microsoft.Insights/actionGroups",
"apiVersion": "2019-06-01",
"name": "[parameters('actionGroupName')]",
"location": "Global",
"properties": {
"groupShortName": "HamlaOps",
"enabled": true,
"emailReceivers": "[parameters('emailReceivers')]"
}
},
{
"type": "Microsoft.Insights/metricAlerts",
"apiVersion": "2018-03-01",
"name": "[parameters('alertName')]",
"location": "global",
"properties": {
"description": "Alert when response time exceeds 1000ms",
"severity": 2,
"enabled": true,
"scopes": [
"[resourceId('Microsoft.Insights/components', parameters('appInsightsName'))]"
],
"evaluationFrequency": "PT5M",
"windowSize": "PT15M",
"criteria": {
"odata.type": "Microsoft.Azure.Monitor.SingleResourceMultipleMetricCriteria",
"allOf": [
{
"name": "Response Time",
"metricName": "requests/duration",
"operator": "GreaterThan",
"threshold": 1000,
"timeAggregation": "Average"
}
]
},
"actions": [
{
"actionGroupId": "[resourceId('Microsoft.Insights/actionGroups', parameters('actionGroupName'))]"
}
]
},
"dependsOn": [
"[resourceId('Microsoft.Insights/actionGroups', parameters('actionGroupName'))]"
]
}
]
}
*/
# مقاييس أداء الأعمال (KPIs) لمنصة Hamla
## مقاييس النمو
- **المستخدمين النشطين شهرياً (MAU)**: عدد المستخدمين الفريدين الذين استخدموا المنصة خلال شهر
- **المستخدمين النشطين يومياً (DAU)**: عدد المستخدمين الفريدين الذين استخدموا المنصة خلال يوم
- **نمو المستخدمين**: نسبة النمو في عدد المستخدمين الجدد شهرياً
- **عدد وكالات السفر**: عدد وكالات السفر النشطة على المنصة
## مقاييس المشاركة
- **متوسط وقت الجلسة**: متوسط المدة التي يقضيها المستخدم في المنصة
- **عدد الصفحات لكل جلسة**: متوسط عدد الصفحات التي يزورها المستخدم في الجلسة الواحدة
- **معدل الارتداد**: نسبة المستخدمين الذين يغادرون المنصة بعد زيارة صفحة واحدة فقط
- **معدل العودة**: نسبة المستخدمين الذين يعودون إلى المنصة خلال 30 يوماً
## مقاييس الحجوزات
- **إجمالي الحجوزات**: عدد الحجوزات التي تمت عبر المنصة
- **متوسط قيمة الحجز**: متوسط قيمة الحجز الواحد
- **معدل التحويل**: نسبة المستخدمين الذين يكملون عملية الحجز من بين الذين بدأوا البحث
- **معدل الإلغاء**: نسبة الحجوزات التي تم إلغاؤها
## مقاييس الرضا
- **درجة رضا المستخدم (CSAT)**: متوسط درجة رضا المستخدمين عن تجربتهم
- **درجة صافي الترويج (NPS)**: مدى استعداد المستخدمين لترشيح المنصة للآخرين
- **متوسط تقييمات وكالات السفر**: متوسط التقييمات التي تحصل عليها وكالات السفر
- **معدل الشكاوى**: نسبة الحجوزات التي تم تقديم شكاوى بشأنها
## مقاييس الأداء التقني
- **وقت استجابة API**: متوسط وقت استجابة الطلبات
- **معدل الأخطاء**: نسبة الطلبات التي تؤدي إلى أخطاء
- **وقت تشغيل النظام**: نسبة الوقت الذي يكون فيه النظام متاحاً ويعمل بشكل صحيح
- **مؤشر أداء الخدمة (SLI)**: النسبة المئوية للعمليات التي تلبي هدف مستوى الخدمة
3. Future Roadmap
التنفيذ:
- وضع خارطة طريق للتطوير المستقبلي
- تخطيط الميزات الجديدة والتحسينات
- تحديد الجدول الزمني للإصدارات المستقبلية
# خارطة طريق منصة Hamla - 2023/2024
## الربع الأول (Q1)
### إطلاق المنصة وتثبيتها
- [x] إطلاق النسخة الأولى للمنصة (MVP)
- [x] تثبيت واختبار النظام في بيئة الإنتاج
- [x] حل المشكلات الرئيسية بعد الإطلاق
- [x] تحسين الأداء والاستقرار
### تحسينات الأمان والاستقرار
- [ ] تحسين نظام المصادقة وإضافة التحقق بخطوتين
- [ ] تحسين آليات المراقبة والتنبيه
- [ ] تعزيز أمان البيانات والامتثال للوائح
- [ ] زيادة قدرة النظام على التحمل وقابليته للتوسع
## الربع الثاني (Q2)
### تطبيق الهاتف المحمول
- [ ] إطلاق تطبيق iOS وAndroid
- [ ] دعم الإشعارات المباشرة على الأجهزة المحمولة
- [ ] دعم وضع عدم الاتصال للحجوزات المؤكدة
- [ ] مشاركة الرحلات عبر تطبيقات التواصل الاجتماعي
### إدارة العلاقات مع العملاء
- [ ] نظام المحادثات المباشرة للدعم الفني
- [ ] نظام تذاكر للمشكلات والاستفسارات
- [ ] برنامج الولاء للمستخدمين المتكررين
- [ ] استطلاعات الرضا المستمرة بعد الرحلات
### توسيع نطاق الوكالات
- [ ] واجهة إدارة متقدمة للوكالات
- [ ] أدوات تحليلية للوكالات لتحسين عروضها
- [ ] نظام عمولات مرن
- [ ] API للتكامل مع أنظمة الوكالات الخارجية
## الربع الثالث (Q3)
### نظام الدفع المتكامل
- [ ] دمج بوابات دفع متعددة
- [ ] خيارات الدفع بالتقسيط
- [ ] نظام استرداد الأموال الآلي
- [ ] الدفع باستخدام نقاط الولاء
### توسيع المحتوى والتجربة
- [ ] إضافة صفحات تفصيلية للوجهات
- [ ] محتوى إرشادي للسفر والوجهات
- [ ] نظام تقييم وتعليقات للوجهات
- [ ] معرض صور ومقاطع فيديو للرحلات والوجهات
### تحسين البحث والتوصيات
- [ ] محرك بحث ذكي مع التصحيح التلقائي
- [ ] فلاتر بحث متقدمة إضافية
- [ ] نظام توصيات شخصية بناءً على تفضيلات المستخدم
- [ ] إشعارات ذكية بالعروض المناسبة للمستخدم
## الربع الرابع (Q4)
### الوظائف الاجتماعية
- [ ] إمكانية إنشاء مجموعات سفر
- [ ] مشاركة خطط الرحلات مع الأصدقاء
- [ ] تنظيم رحلات جماعية
- [ ] نظام دعوة الأصدقاء مع مكافآت
### الدعم اللغوي والعالمي
- [ ] واجهة متعددة اللغات (العربية والإنجليزية)
- [ ] دعم العملات المتعددة
- [ ] تكييف العروض حسب المنطقة
- [ ] توسيع لدول إضافية في المنطقة
### تكامل خدمات إضافية
- [ ] حجز تذاكر الطيران
- [ ] حجز الفنادق
- [ ] تأمين السفر
- [ ] خدمات النقل من وإلى المطارات
## العام القادم (2024) - الخطة العامة
### الربع الأول (Q1 2024)
- [ ] إطلاق نظام الحجز الجماعي للشركات
- [ ] منصة للمؤتمرات والفعاليات
- [ ] برنامج للمسوقين بالعمولة
- [ ] تحليلات متقدمة وذكاء أعمال للإدارة
### الربع الثاني (Q2 2024)
- [ ] واجهة برمجة التطبيقات (API) الرسمية للشركاء
- [ ] تكامل مع منصات حجز عالمية
- [ ] نظام توصيات قائم على الذكاء الاصطناعي
- [ ] روبوت محادثة ذكي للمساعدة
### الربع الثالث (Q3 2024)
- [ ] خدمات VIP المخصصة
- [ ] خيارات الرحلات المستدامة والصديقة للبيئة
- [ ] تكامل مع تقنية الواقع المعزز لاستكشاف الوجهات
- [ ] أدوات تخطيط الرحلات المتقدمة
### الربع الرابع (Q4 2024)
- [ ] التوسع لمناطق جغرافية جديدة
- [ ] إطلاق برنامج شراكة استراتيجي
- [ ] نظام متقدم للتنبؤ بالطلب والأسعار
- [ ] إعادة تصميم شاملة للواجهة بناءً على تجربة المستخدم
4. Feedback and Continuous Improvement Process
التنفيذ:
- إعداد نظام لجمع وتحليل ملاحظات المستخدمين
- وضع عملية مستمرة للتحسين بناءً على البيانات
- قياس تأثير التحسينات على تجربة المستخدم
# عملية جمع الملاحظات والتحسين المستمر لمنصة Hamla
## قنوات جمع الملاحظات
### ملاحظات المستخدمين المباشرة
- **استطلاعات داخل التطبيق**: استطلاعات قصيرة تظهر بعد إتمام عمليات محددة
- **استطلاعات البريد الإلكتروني**: استطلاعات مفصلة ترسل بعد الرحلات
- **زر "شاركنا رأيك"**: متاح في جميع صفحات المنصة
- **آراء بعد التقييم**: طلب تعليق عند تقييم الوكالات أو الرحلات
### ملاحظات غير مباشرة
- **تحليل سلوك المستخدم**: دراسة كيفية تفاعل المستخدمين مع المنصة
- **تحليل معدلات التحويل**: تحديد النقاط التي يتراجع فيها المستخدمون
- **تحليل كلمات البحث**: فهم ما يبحث عنه المستخدمون
- **تحليل المحادثات مع الدعم**: استخلاص مشاكل ومقترحات متكررة
### ملاحظات وكالات السفر
- **استطلاعات دورية**: استطلاعات ربع سنوية لوكالات السفر
- **اجتماعات مع الشركاء الرئيسيين**: جلسات دورية للحصول على آراء متعمقة
- **تقارير أداء وكالات السفر**: تحليل البيانات لتحديد نقاط التحسين
## عملية معالجة الملاحظات
### 1. الجمع والتصنيف
- جمع الملاحظات من جميع القنوات
- تصنيف الملاحظات حسب:
- نوع الملاحظة (مشكلة، اقتراح، تعليق)
- منطقة المنصة (البحث، الحجز، الدفع، إلخ)
- خطورة المشكلة (حرجة، عالية، متوسطة، منخفضة)
- مصدر الملاحظة (مستخدم، وكالة، فريق داخلي)
### 2. التحليل والأولويات
- تحليل تكرار الملاحظات لتحديد المشكلات الشائعة
- تقييم تأثير المشكلات على تجربة المستخدم
- تحديد الفرص لتحسين المنصة
- وضع أولويات للتحسينات بناءً على:
- التأثير على تجربة المستخدم
- التأثير على الأعمال (الإيرادات، الاحتفاظ)
- الجهد المطلوب للتنفيذ
- التوافق مع استراتيجية المنصة
### 3. التخطيط والتنفيذ
- إضافة التحسينات ذات الأولوية العالية إلى خطة العمل
- تحديد نطاق التحسينات ومتطلباتها
- تخصيص الموارد وتحديد الجداول الزمنية
- تنفيذ التحسينات وفق منهجية Agile
### 4. الاختبار والنشر
- اختبار التحسينات مع مجموعة محدودة من المستخدمين
- جمع ملاحظات أولية وإجراء تعديلات إذا لزم الأمر
- نشر التحسينات لجميع المستخدمين
- مراقبة تأثير التحسينات بعد النشر
### 5. التواصل والقياس
- إبلاغ المستخدمين بالتحسينات الجديدة
- التواصل المباشر مع مقدمي الملاحظات المهمة
- قياس تأثير التحسينات على:
- رضا المستخدمين
- مقاييس الأداء الرئيسية (KPIs)
- معدلات التحويل والاحتفاظ
## إدارة دورة التحسين المستمر
### اجتماعات منتظمة
- **اجتماع أسبوعي لفريق المنتج**: مراجعة الملاحظات الواردة والتقدم في التحسينات
- **اجتماع شهري للفريق القيادي**: مراجعة المقاييس والقرارات الاستراتيجية
- **اجتماع ربع سنوي للمراجعة الشاملة**: تقييم التقدم العام وتعديل الخطط
### التوثيق والشفافية
- توثيق جميع الملاحظات المستلمة وإجراءات المتابعة
- مشاركة خارطة طريق التحسينات مع المستخدمين
- نشر تقارير دورية عن التحسينات المنفذة
### المكافأة والتحفيز
- تقديم مكافآت للمستخدمين الذين يقدمون ملاحظات قيمة
- الاعتراف بمساهمات الفريق في تحسين المنصة
- ربط أهداف الأداء بتحسين تجربة المستخدم
الخلاصة: إنجاز مشروع Hamla 🏆
هذه الخطة المفصلة توفر مسارًا شاملًا لتطوير وإطلاق منصة Hamla من البداية إلى النهاية. بتتبع هذه الخطوات التفصيلية، ستتمكن من:
- بناء منصة متكاملة تربط المسافرين بوكالات السفر
- ضمان بنية تحتية آمنة وقابلة للتوسع
- تقديم تجربة مستخدم سلسة ومميزة
- إنشاء نظام مرن يمكن تطويره وتوسيعه مع نمو الأعمال
تذكر أن النجاح الحقيقي للمشروع لا يكمن فقط في الإطلاق الأولي، بل في الدعم المستمر والتحسين والتطوير استنادًا إلى احتياجات المستخدمين الفعلية والتغيرات في سوق السفر.