f3701c5893
Now that the origin serves directly (no CDN compressing for us): - Add gzip + brotli response compression — homepage HTML 84KB -> ~25KB (~71% smaller), big Core Web Vitals / crawl-budget win - Baseline security headers on every response: X-Content-Type-Options, X-Frame-Options, Referrer-Policy (no HSTS yet — cert just stabilised) - Long-cache immutable GUID uploads (Cache-Control max-age=30d,immutable) Verified locally: gzip+br negotiated, headers present, uploads cached. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1548 lines
109 KiB
C#
1548 lines
109 KiB
C#
using System.IdentityModel.Tokens.Jwt;
|
||
using System.Security.Claims;
|
||
using System.Text;
|
||
using System.Text.Encodings.Web;
|
||
using System.Text.Unicode;
|
||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.IdentityModel.Tokens;
|
||
using DrSousan.Api.Data;
|
||
using DrSousan.Api.Models;
|
||
|
||
// Container HEALTHCHECK self-probe — the aspnet image ships without curl/wget,
|
||
// so we run a short-lived `dotnet DrSousan.Api.dll --healthcheck` process instead.
|
||
if (args.Contains("--healthcheck"))
|
||
{
|
||
using var probe = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
|
||
try
|
||
{
|
||
var resp = await probe.GetAsync("http://localhost:8080/healthz");
|
||
return resp.IsSuccessStatusCode ? 0 : 1;
|
||
}
|
||
catch { return 1; }
|
||
}
|
||
|
||
var builder = WebApplication.CreateBuilder(args);
|
||
var config = builder.Configuration;
|
||
|
||
// ── Services ─────────────────────────────────────────────────────────────────
|
||
builder.Services.AddDbContext<AppDbContext>(opt =>
|
||
opt.UseSqlite(config.GetConnectionString("Default")));
|
||
|
||
var jwtKey = Encoding.UTF8.GetBytes(config["Jwt:Key"]!);
|
||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||
.AddJwtBearer(opt =>
|
||
{
|
||
opt.TokenValidationParameters = new TokenValidationParameters
|
||
{
|
||
ValidateIssuer = true,
|
||
ValidateAudience = true,
|
||
ValidateLifetime = true,
|
||
ValidateIssuerSigningKey = true,
|
||
ValidIssuer = config["Jwt:Issuer"],
|
||
ValidAudience = config["Jwt:Audience"],
|
||
IssuerSigningKey = new SymmetricSecurityKey(jwtKey)
|
||
};
|
||
});
|
||
|
||
builder.Services.AddAuthorization();
|
||
builder.Services.AddCors(opt =>
|
||
opt.AddDefaultPolicy(p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()));
|
||
|
||
// Razor Pages for SSR public pages
|
||
builder.Services.AddRazorPages();
|
||
// Don't entity-encode non-ASCII (Persian) or chars like '+' in markup output.
|
||
// Default encoder turns "application/ld+json" into "application/ld+json" and
|
||
// Persian text into \XX; entities — valid but bloated and trips some validators.
|
||
builder.Services.AddSingleton(HtmlEncoder.Create(UnicodeRanges.All));
|
||
|
||
// Fix circular JSON references (BlogPost ↔ BlogCategory)
|
||
builder.Services.ConfigureHttpJsonOptions(opts =>
|
||
opts.SerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles);
|
||
|
||
// Response compression (gzip + brotli) — origin nginx no longer sits behind a
|
||
// compressing CDN, so HTML/CSS/JS were served uncompressed (~80KB). Cuts payload ~75%.
|
||
builder.Services.AddResponseCompression(opts =>
|
||
{
|
||
opts.EnableForHttps = true;
|
||
opts.Providers.Add<Microsoft.AspNetCore.ResponseCompression.BrotliCompressionProvider>();
|
||
opts.Providers.Add<Microsoft.AspNetCore.ResponseCompression.GzipCompressionProvider>();
|
||
opts.MimeTypes = Microsoft.AspNetCore.ResponseCompression.ResponseCompressionDefaults.MimeTypes
|
||
.Concat(new[] { "application/ld+json", "image/svg+xml" });
|
||
});
|
||
builder.Services.Configure<Microsoft.AspNetCore.ResponseCompression.BrotliCompressionProviderOptions>(
|
||
o => o.Level = System.IO.Compression.CompressionLevel.Fastest);
|
||
builder.Services.Configure<Microsoft.AspNetCore.ResponseCompression.GzipCompressionProviderOptions>(
|
||
o => o.Level = System.IO.Compression.CompressionLevel.Fastest);
|
||
|
||
// ── Build ─────────────────────────────────────────────────────────────────────
|
||
var app = builder.Build();
|
||
|
||
// In production return a clean 500 page rather than an unhandled exception dump
|
||
// (Googlebot seeing raw 5xx responses causes GSC "Server error" indexing failures).
|
||
if (!app.Environment.IsDevelopment())
|
||
app.UseExceptionHandler("/error");
|
||
|
||
app.UseResponseCompression();
|
||
|
||
// Baseline security headers on every response (safe defaults; no HSTS yet — the
|
||
// cert was just stabilised, so we avoid forcing HTTPS pinning until it's proven).
|
||
app.Use(async (ctx, next) =>
|
||
{
|
||
var h = ctx.Response.Headers;
|
||
h["X-Content-Type-Options"] = "nosniff";
|
||
h["X-Frame-Options"] = "SAMEORIGIN";
|
||
h["Referrer-Policy"] = "strict-origin-when-cross-origin";
|
||
await next();
|
||
});
|
||
|
||
app.UseCors();
|
||
app.UseDefaultFiles(); // serves /admin/index.html for /admin/ (wwwroot/index.html deleted → no conflict with Razor Pages)
|
||
app.UseStaticFiles(new StaticFileOptions
|
||
{
|
||
// Uploaded files use immutable GUID names → safe to cache aggressively.
|
||
OnPrepareResponse = ctx =>
|
||
{
|
||
var p = ctx.Context.Request.Path.Value ?? "";
|
||
if (p.StartsWith("/uploads/", StringComparison.OrdinalIgnoreCase))
|
||
ctx.Context.Response.Headers["Cache-Control"] = "public,max-age=2592000,immutable";
|
||
}
|
||
});
|
||
app.UseAuthentication();
|
||
app.UseAuthorization();
|
||
|
||
// ── DB Init & Seed ────────────────────────────────────────────────────────────
|
||
await using (var scope = app.Services.CreateAsyncScope())
|
||
{
|
||
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||
await db.Database.EnsureCreatedAsync();
|
||
// Ensure new Service columns exist on existing databases (safe ADD COLUMN)
|
||
foreach (var col in new[] { "BeforeImageUrl", "AfterImageUrl" })
|
||
{
|
||
try { await db.Database.ExecuteSqlRawAsync($"ALTER TABLE Services ADD COLUMN \"{col}\" TEXT NOT NULL DEFAULT ''"); }
|
||
catch { /* column already exists — ignore */ }
|
||
}
|
||
// Ensure Faqs table exists on databases created before this model was added
|
||
try
|
||
{
|
||
await db.Database.ExecuteSqlRawAsync(
|
||
"""
|
||
CREATE TABLE IF NOT EXISTS "Faqs" (
|
||
"Id" INTEGER NOT NULL CONSTRAINT "PK_Faqs" PRIMARY KEY AUTOINCREMENT,
|
||
"Question" TEXT NOT NULL DEFAULT '',
|
||
"Answer" TEXT NOT NULL DEFAULT '',
|
||
"Order" INTEGER NOT NULL DEFAULT 0,
|
||
"IsActive" INTEGER NOT NULL DEFAULT 1,
|
||
"CreatedAt" TEXT NOT NULL DEFAULT ''
|
||
)
|
||
""");
|
||
}
|
||
catch { /* already exists */ }
|
||
// Ensure Patient tables exist
|
||
try {
|
||
await db.Database.ExecuteSqlRawAsync("""
|
||
CREATE TABLE IF NOT EXISTS "Patients" (
|
||
"Id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||
"FullName" TEXT NOT NULL DEFAULT '',
|
||
"PhoneNumber" TEXT NOT NULL DEFAULT '',
|
||
"Email" TEXT NOT NULL DEFAULT '',
|
||
"Age" INTEGER NOT NULL DEFAULT 0,
|
||
"Weight" REAL NOT NULL DEFAULT 0,
|
||
"Height" REAL NOT NULL DEFAULT 0,
|
||
"Gender" TEXT NOT NULL DEFAULT '',
|
||
"BloodType" TEXT NOT NULL DEFAULT '',
|
||
"DiseaseHistory" TEXT NOT NULL DEFAULT '',
|
||
"Allergies" TEXT NOT NULL DEFAULT '',
|
||
"Medications" TEXT NOT NULL DEFAULT '',
|
||
"Notes" TEXT NOT NULL DEFAULT '',
|
||
"Category" TEXT NOT NULL DEFAULT 'beauty',
|
||
"IsActive" INTEGER NOT NULL DEFAULT 1,
|
||
"CreatedAt" TEXT NOT NULL DEFAULT ''
|
||
)
|
||
""");
|
||
} catch { }
|
||
try {
|
||
await db.Database.ExecuteSqlRawAsync("""
|
||
CREATE TABLE IF NOT EXISTS "PatientVisits" (
|
||
"Id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||
"PatientId" INTEGER NOT NULL,
|
||
"Title" TEXT NOT NULL DEFAULT '',
|
||
"Content" TEXT NOT NULL DEFAULT '',
|
||
"Prescription" TEXT NOT NULL DEFAULT '',
|
||
"VisitType" TEXT NOT NULL DEFAULT 'ویزیت',
|
||
"VisitDate" TEXT NOT NULL DEFAULT '',
|
||
"NextVisitDate" TEXT,
|
||
"CreatedAt" TEXT NOT NULL DEFAULT '',
|
||
FOREIGN KEY ("PatientId") REFERENCES "Patients"("Id") ON DELETE CASCADE
|
||
)
|
||
""");
|
||
} catch { }
|
||
try {
|
||
await db.Database.ExecuteSqlRawAsync("""
|
||
CREATE TABLE IF NOT EXISTS "HealthRequests" (
|
||
"Id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||
"TrackingCode" TEXT NOT NULL DEFAULT '',
|
||
"FullName" TEXT NOT NULL DEFAULT '',
|
||
"PhoneNumber" TEXT NOT NULL DEFAULT '',
|
||
"Email" TEXT NOT NULL DEFAULT '',
|
||
"Message" TEXT NOT NULL DEFAULT '',
|
||
"Category" TEXT NOT NULL DEFAULT 'beauty',
|
||
"IsHandled" INTEGER NOT NULL DEFAULT 0,
|
||
"Diagnosis" TEXT NOT NULL DEFAULT '',
|
||
"DoctorReply" TEXT NOT NULL DEFAULT '',
|
||
"RepliedAt" TEXT,
|
||
"CreatedAt" TEXT NOT NULL DEFAULT ''
|
||
)
|
||
""");
|
||
} catch { }
|
||
// Add new columns to existing HealthRequests table (safe migration)
|
||
foreach (var col in new[] {
|
||
("TrackingCode", "TEXT NOT NULL DEFAULT ''"),
|
||
("Diagnosis", "TEXT NOT NULL DEFAULT ''"),
|
||
("DoctorReply", "TEXT NOT NULL DEFAULT ''"),
|
||
("RepliedAt", "TEXT") })
|
||
{
|
||
try { await db.Database.ExecuteSqlRawAsync(
|
||
$"ALTER TABLE HealthRequests ADD COLUMN \"{col.Item1}\" {col.Item2}"); }
|
||
catch { }
|
||
}
|
||
|
||
await SeedAsync(db);
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════════════════════════
|
||
// ROUTES
|
||
// ══════════════════════════════════════════════════════════════════════════════
|
||
|
||
// ── Auth ──────────────────────────────────────────────────────────────────────
|
||
app.MapPost("/api/auth/login", async (LoginRequest req, AppDbContext db) =>
|
||
{
|
||
var stored = await db.SiteSettings
|
||
.FirstOrDefaultAsync(s => s.Section == "system" && s.Key == "adminPassword");
|
||
var configPw = config["Admin:Password"]!;
|
||
var currentPw = stored?.Value ?? configPw;
|
||
// configPw always works as a master-reset override (change .env → redeploy → login)
|
||
if (req.Username != config["Admin:Username"] ||
|
||
(req.Password != currentPw && req.Password != configPw))
|
||
return Results.Unauthorized();
|
||
|
||
var claims = new[] { new Claim(ClaimTypes.Name, req.Username) };
|
||
var key = new SymmetricSecurityKey(jwtKey);
|
||
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||
var token = new JwtSecurityToken(
|
||
issuer: config["Jwt:Issuer"],
|
||
audience: config["Jwt:Audience"],
|
||
claims: claims,
|
||
expires: DateTime.UtcNow.AddHours(12),
|
||
signingCredentials: creds);
|
||
|
||
return Results.Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
|
||
});
|
||
|
||
app.MapPost("/api/auth/change-password", async (ChangePasswordRequest req, AppDbContext db) =>
|
||
{
|
||
var stored = await db.SiteSettings
|
||
.FirstOrDefaultAsync(s => s.Section == "system" && s.Key == "adminPassword");
|
||
var currentPw = stored?.Value ?? config["Admin:Password"];
|
||
|
||
if (req.CurrentPassword != currentPw)
|
||
return Results.BadRequest(new { message = "رمز عبور فعلی صحیح نیست." });
|
||
if (string.IsNullOrWhiteSpace(req.NewPassword) || req.NewPassword.Length < 6)
|
||
return Results.BadRequest(new { message = "رمز عبور جدید باید حداقل ۶ کاراکتر باشد." });
|
||
|
||
if (stored is null)
|
||
{
|
||
db.SiteSettings.Add(new SiteSetting { Section = "system", Key = "adminPassword", Value = req.NewPassword });
|
||
}
|
||
else
|
||
{
|
||
stored.Value = req.NewPassword;
|
||
}
|
||
await db.SaveChangesAsync();
|
||
return Results.Ok(new { message = "رمز عبور با موفقیت تغییر کرد." });
|
||
}).RequireAuthorization();
|
||
|
||
// ── Site Settings ─────────────────────────────────────────────────────────────
|
||
var settingsGroup = app.MapGroup("/api/settings");
|
||
|
||
settingsGroup.MapGet("/{section}", async (string section, AppDbContext db) =>
|
||
{
|
||
var rows = await db.SiteSettings.Where(s => s.Section == section).ToListAsync();
|
||
return Results.Ok(rows.ToDictionary(s => s.Key, s => s.Value));
|
||
});
|
||
|
||
settingsGroup.MapPut("/{section}", async (string section, BulkSettingsDto dto, AppDbContext db) =>
|
||
{
|
||
if (dto?.Settings is null) return Results.BadRequest("Invalid payload.");
|
||
foreach (var (k, v) in dto.Settings)
|
||
{
|
||
var row = await db.SiteSettings.FirstOrDefaultAsync(s => s.Section == section && s.Key == k);
|
||
if (row is null) db.SiteSettings.Add(new SiteSetting { Section = section, Key = k, Value = v ?? "" });
|
||
else row.Value = v ?? "";
|
||
}
|
||
await db.SaveChangesAsync();
|
||
return Results.Ok();
|
||
}).RequireAuthorization();
|
||
|
||
// ── Services ──────────────────────────────────────────────────────────────────
|
||
var svcGroup = app.MapGroup("/api/services");
|
||
|
||
svcGroup.MapGet("/", async (AppDbContext db) =>
|
||
Results.Ok(await db.Services.Where(s => s.IsActive).OrderBy(s => s.Order).ToListAsync()));
|
||
|
||
svcGroup.MapGet("/all", async (AppDbContext db) =>
|
||
Results.Ok(await db.Services.OrderBy(s => s.Order).ToListAsync()))
|
||
.RequireAuthorization();
|
||
|
||
svcGroup.MapPost("/", async (Service svc, AppDbContext db) =>
|
||
{
|
||
svc.CreatedAt = DateTime.UtcNow;
|
||
db.Services.Add(svc);
|
||
await db.SaveChangesAsync();
|
||
return Results.Created($"/api/services/{svc.Id}", svc);
|
||
}).RequireAuthorization();
|
||
|
||
svcGroup.MapPut("/{id:int}", async (int id, Service updated, AppDbContext db) =>
|
||
{
|
||
var svc = await db.Services.FindAsync(id);
|
||
if (svc is null) return Results.NotFound();
|
||
svc.Title = updated.Title; svc.Description = updated.Description;
|
||
svc.IconSvg = updated.IconSvg; svc.Order = updated.Order; svc.IsActive = updated.IsActive;
|
||
svc.BeforeImageUrl = updated.BeforeImageUrl;
|
||
svc.AfterImageUrl = updated.AfterImageUrl;
|
||
await db.SaveChangesAsync();
|
||
return Results.Ok(svc);
|
||
}).RequireAuthorization();
|
||
|
||
svcGroup.MapDelete("/{id:int}", async (int id, AppDbContext db) =>
|
||
{
|
||
var svc = await db.Services.FindAsync(id);
|
||
if (svc is null) return Results.NotFound();
|
||
db.Services.Remove(svc); await db.SaveChangesAsync();
|
||
return Results.NoContent();
|
||
}).RequireAuthorization();
|
||
|
||
// ── Gallery ───────────────────────────────────────────────────────────────────
|
||
var galleryGroup = app.MapGroup("/api/gallery");
|
||
|
||
galleryGroup.MapGet("/", async (string? category, AppDbContext db) =>
|
||
{
|
||
var q = db.GalleryItems.Where(g => g.IsActive);
|
||
if (!string.IsNullOrEmpty(category)) q = q.Where(g => g.Category == category);
|
||
return Results.Ok(await q.OrderBy(g => g.Order).ToListAsync());
|
||
});
|
||
|
||
galleryGroup.MapGet("/all", async (AppDbContext db) =>
|
||
Results.Ok(await db.GalleryItems.OrderBy(g => g.Order).ToListAsync()))
|
||
.RequireAuthorization();
|
||
|
||
galleryGroup.MapPost("/", async (GalleryItem item, AppDbContext db) =>
|
||
{
|
||
item.CreatedAt = DateTime.UtcNow; db.GalleryItems.Add(item);
|
||
await db.SaveChangesAsync();
|
||
return Results.Created($"/api/gallery/{item.Id}", item);
|
||
}).RequireAuthorization();
|
||
|
||
galleryGroup.MapPut("/{id:int}", async (int id, GalleryItem updated, AppDbContext db) =>
|
||
{
|
||
var item = await db.GalleryItems.FindAsync(id);
|
||
if (item is null) return Results.NotFound();
|
||
item.ImageUrl = updated.ImageUrl; item.BeforeImageUrl = updated.BeforeImageUrl;
|
||
item.AfterImageUrl = updated.AfterImageUrl; item.Category = updated.Category;
|
||
item.Caption = updated.Caption; item.Order = updated.Order; item.IsActive = updated.IsActive;
|
||
await db.SaveChangesAsync();
|
||
return Results.Ok(item);
|
||
}).RequireAuthorization();
|
||
|
||
galleryGroup.MapDelete("/{id:int}", async (int id, AppDbContext db) =>
|
||
{
|
||
var item = await db.GalleryItems.FindAsync(id);
|
||
if (item is null) return Results.NotFound();
|
||
db.GalleryItems.Remove(item); await db.SaveChangesAsync();
|
||
return Results.NoContent();
|
||
}).RequireAuthorization();
|
||
|
||
// ── Testimonials ──────────────────────────────────────────────────────────────
|
||
var testimGroup = app.MapGroup("/api/testimonials");
|
||
|
||
testimGroup.MapGet("/", async (AppDbContext db) =>
|
||
Results.Ok(await db.Testimonials.Where(t => t.IsActive).OrderByDescending(t => t.CreatedAt).ToListAsync()));
|
||
|
||
testimGroup.MapGet("/all", async (AppDbContext db) =>
|
||
Results.Ok(await db.Testimonials.OrderByDescending(t => t.CreatedAt).ToListAsync()))
|
||
.RequireAuthorization();
|
||
|
||
testimGroup.MapPost("/", async (Testimonial t, AppDbContext db) =>
|
||
{
|
||
t.CreatedAt = DateTime.UtcNow; db.Testimonials.Add(t);
|
||
await db.SaveChangesAsync();
|
||
return Results.Created($"/api/testimonials/{t.Id}", t);
|
||
}).RequireAuthorization();
|
||
|
||
testimGroup.MapPut("/{id:int}", async (int id, Testimonial updated, AppDbContext db) =>
|
||
{
|
||
var t = await db.Testimonials.FindAsync(id);
|
||
if (t is null) return Results.NotFound();
|
||
t.AuthorName = updated.AuthorName; t.AuthorEmoji = updated.AuthorEmoji;
|
||
t.Text = updated.Text; t.Rating = updated.Rating; t.Date = updated.Date; t.IsActive = updated.IsActive;
|
||
await db.SaveChangesAsync();
|
||
return Results.Ok(t);
|
||
}).RequireAuthorization();
|
||
|
||
testimGroup.MapDelete("/{id:int}", async (int id, AppDbContext db) =>
|
||
{
|
||
var t = await db.Testimonials.FindAsync(id);
|
||
if (t is null) return Results.NotFound();
|
||
db.Testimonials.Remove(t); await db.SaveChangesAsync();
|
||
return Results.NoContent();
|
||
}).RequireAuthorization();
|
||
|
||
// ── Blog Categories ───────────────────────────────────────────────────────────
|
||
var catsGroup = app.MapGroup("/api/blog/categories");
|
||
|
||
catsGroup.MapGet("/", async (AppDbContext db) =>
|
||
Results.Ok(await db.BlogCategories.Include(c => c.Posts)
|
||
.Select(c => new { c.Id, c.Name, c.Slug, c.Description,
|
||
PostCount = c.Posts.Count(p => p.IsPublished) })
|
||
.ToListAsync()));
|
||
|
||
catsGroup.MapPost("/", async (BlogCategory cat, AppDbContext db) =>
|
||
{
|
||
cat.Slug = Slugify(cat.Name); db.BlogCategories.Add(cat);
|
||
await db.SaveChangesAsync();
|
||
return Results.Created($"/api/blog/categories/{cat.Id}", cat);
|
||
}).RequireAuthorization();
|
||
|
||
catsGroup.MapPut("/{id:int}", async (int id, BlogCategory updated, AppDbContext db) =>
|
||
{
|
||
var cat = await db.BlogCategories.FindAsync(id);
|
||
if (cat is null) return Results.NotFound();
|
||
cat.Name = updated.Name; cat.Description = updated.Description;
|
||
cat.Slug = string.IsNullOrEmpty(updated.Slug) ? Slugify(updated.Name) : updated.Slug;
|
||
await db.SaveChangesAsync();
|
||
return Results.Ok(cat);
|
||
}).RequireAuthorization();
|
||
|
||
catsGroup.MapDelete("/{id:int}", async (int id, AppDbContext db) =>
|
||
{
|
||
var cat = await db.BlogCategories.FindAsync(id);
|
||
if (cat is null) return Results.NotFound();
|
||
db.BlogCategories.Remove(cat); await db.SaveChangesAsync();
|
||
return Results.NoContent();
|
||
}).RequireAuthorization();
|
||
|
||
// ── Blog Posts ────────────────────────────────────────────────────────────────
|
||
var postsGroup = app.MapGroup("/api/blog/posts");
|
||
|
||
// Public paginated list
|
||
postsGroup.MapGet("/", async (int page, int pageSize, string? category, string? search, AppDbContext db) =>
|
||
{
|
||
page = page < 1 ? 1 : page;
|
||
pageSize = pageSize is < 1 or > 50 ? 10 : pageSize;
|
||
var q = db.BlogPosts.Include(p => p.Category).Where(p => p.IsPublished);
|
||
if (!string.IsNullOrEmpty(category)) q = q.Where(p => p.Category != null && p.Category.Slug == category);
|
||
if (!string.IsNullOrEmpty(search)) q = q.Where(p => p.Title.Contains(search) || p.Excerpt.Contains(search));
|
||
var total = await q.CountAsync();
|
||
var items = await q.OrderByDescending(p => p.PublishedAt)
|
||
.Skip((page - 1) * pageSize).Take(pageSize)
|
||
.Select(p => new { p.Id, p.Title, p.Slug, p.Excerpt, p.FeaturedImage,
|
||
p.Author, p.PublishedAt, p.ViewCount, p.ReadingTimeMinutes,
|
||
Category = p.Category == null ? null : new { p.Category.Id, p.Category.Name, p.Category.Slug }
|
||
}).ToListAsync();
|
||
return Results.Ok(new { total, page, pageSize, items });
|
||
});
|
||
|
||
// Admin list
|
||
postsGroup.MapGet("/admin", async (AppDbContext db) =>
|
||
Results.Ok(await db.BlogPosts.Include(p => p.Category)
|
||
.OrderByDescending(p => p.CreatedAt)
|
||
.Select(p => new { p.Id, p.Title, p.Slug, p.IsPublished, p.PublishedAt,
|
||
p.ViewCount, p.CreatedAt, p.FocusKeyword,
|
||
Category = p.Category == null ? null : new { p.Category.Id, p.Category.Name }
|
||
}).ToListAsync()))
|
||
.RequireAuthorization();
|
||
|
||
// Public: by slug (increments view count)
|
||
postsGroup.MapGet("/{slug}", async (string slug, AppDbContext db) =>
|
||
{
|
||
var post = await db.BlogPosts.Include(p => p.Category)
|
||
.FirstOrDefaultAsync(p => p.Slug == slug && p.IsPublished);
|
||
if (post is null) return Results.NotFound();
|
||
post.ViewCount++;
|
||
await db.SaveChangesAsync();
|
||
return Results.Ok(post);
|
||
});
|
||
|
||
// Admin: full post by id
|
||
postsGroup.MapGet("/id/{id:int}", async (int id, AppDbContext db) =>
|
||
{
|
||
var post = await db.BlogPosts.Include(p => p.Category).FirstOrDefaultAsync(p => p.Id == id);
|
||
return post is null ? Results.NotFound() : Results.Ok(post);
|
||
}).RequireAuthorization();
|
||
|
||
postsGroup.MapPost("/", async (BlogPost post, AppDbContext db) =>
|
||
{
|
||
post.Slug = string.IsNullOrEmpty(post.Slug) ? Slugify(post.Title) : post.Slug;
|
||
post.CreatedAt = DateTime.UtcNow; post.UpdatedAt = DateTime.UtcNow;
|
||
if (post.IsPublished && post.PublishedAt is null) post.PublishedAt = DateTime.UtcNow;
|
||
post.ReadingTimeMinutes = EstimateReadingTime(post.Content);
|
||
db.BlogPosts.Add(post); await db.SaveChangesAsync();
|
||
return Results.Created($"/api/blog/posts/{post.Slug}", post);
|
||
}).RequireAuthorization();
|
||
|
||
postsGroup.MapPut("/{id:int}", async (int id, BlogPost updated, AppDbContext db) =>
|
||
{
|
||
var post = await db.BlogPosts.FindAsync(id);
|
||
if (post is null) return Results.NotFound();
|
||
post.Title = updated.Title;
|
||
post.Slug = string.IsNullOrEmpty(updated.Slug) ? Slugify(updated.Title) : updated.Slug;
|
||
post.Excerpt = updated.Excerpt; post.Content = updated.Content;
|
||
post.FeaturedImage = updated.FeaturedImage; post.Author = updated.Author;
|
||
post.MetaTitle = updated.MetaTitle; post.MetaDescription = updated.MetaDescription;
|
||
post.FocusKeyword = updated.FocusKeyword; post.Keywords = updated.Keywords;
|
||
post.OgImage = updated.OgImage; post.CategoryId = updated.CategoryId;
|
||
post.ArticleType = updated.ArticleType; post.IsPublished = updated.IsPublished;
|
||
post.UpdatedAt = DateTime.UtcNow;
|
||
post.ReadingTimeMinutes = EstimateReadingTime(updated.Content);
|
||
if (post.IsPublished && post.PublishedAt is null) post.PublishedAt = DateTime.UtcNow;
|
||
await db.SaveChangesAsync();
|
||
return Results.Ok(post);
|
||
}).RequireAuthorization();
|
||
|
||
postsGroup.MapDelete("/{id:int}", async (int id, AppDbContext db) =>
|
||
{
|
||
var post = await db.BlogPosts.FindAsync(id);
|
||
if (post is null) return Results.NotFound();
|
||
db.BlogPosts.Remove(post); await db.SaveChangesAsync();
|
||
return Results.NoContent();
|
||
}).RequireAuthorization();
|
||
|
||
// ── FAQs ──────────────────────────────────────────────────────────────────────
|
||
var faqGroup = app.MapGroup("/api/faqs");
|
||
|
||
faqGroup.MapGet("/", async (AppDbContext db) =>
|
||
Results.Ok(await db.Faqs.Where(f => f.IsActive).OrderBy(f => f.Order).ToListAsync()));
|
||
|
||
faqGroup.MapGet("/all", async (AppDbContext db) =>
|
||
Results.Ok(await db.Faqs.OrderBy(f => f.Order).ToListAsync()))
|
||
.RequireAuthorization();
|
||
|
||
faqGroup.MapPost("/", async (Faq faq, AppDbContext db) =>
|
||
{
|
||
faq.CreatedAt = DateTime.UtcNow;
|
||
db.Faqs.Add(faq);
|
||
await db.SaveChangesAsync();
|
||
return Results.Created($"/api/faqs/{faq.Id}", faq);
|
||
}).RequireAuthorization();
|
||
|
||
faqGroup.MapPut("/{id:int}", async (int id, Faq updated, AppDbContext db) =>
|
||
{
|
||
var faq = await db.Faqs.FindAsync(id);
|
||
if (faq is null) return Results.NotFound();
|
||
faq.Question = updated.Question;
|
||
faq.Answer = updated.Answer;
|
||
faq.Order = updated.Order;
|
||
faq.IsActive = updated.IsActive;
|
||
await db.SaveChangesAsync();
|
||
return Results.Ok(faq);
|
||
}).RequireAuthorization();
|
||
|
||
faqGroup.MapDelete("/{id:int}", async (int id, AppDbContext db) =>
|
||
{
|
||
var faq = await db.Faqs.FindAsync(id);
|
||
if (faq is null) return Results.NotFound();
|
||
db.Faqs.Remove(faq);
|
||
await db.SaveChangesAsync();
|
||
return Results.NoContent();
|
||
}).RequireAuthorization();
|
||
|
||
// ── Comments ──────────────────────────────────────────────────────────────────
|
||
|
||
// Public: get approved comments (with admin replies) for a post
|
||
app.MapGet("/api/blog/posts/{slug}/comments", async (string slug, AppDbContext db) =>
|
||
{
|
||
var post = await db.BlogPosts.FirstOrDefaultAsync(p => p.Slug == slug && p.IsPublished);
|
||
if (post is null) return Results.NotFound();
|
||
// Load top-level approved comments with their replies
|
||
var comments = await db.Comments
|
||
.Where(c => c.BlogPostId == post.Id && c.IsApproved && c.ParentId == null)
|
||
.OrderBy(c => c.CreatedAt)
|
||
.Select(c => new {
|
||
c.Id, c.AuthorName, c.Body, c.CreatedAt, c.IsAdminReply,
|
||
Replies = db.Comments
|
||
.Where(r => r.ParentId == c.Id && r.IsApproved)
|
||
.OrderBy(r => r.CreatedAt)
|
||
.Select(r => new { r.Id, r.AuthorName, r.Body, r.CreatedAt, r.IsAdminReply })
|
||
.ToList()
|
||
}).ToListAsync();
|
||
return Results.Ok(comments);
|
||
});
|
||
|
||
// Public: submit a comment (pending approval)
|
||
app.MapPost("/api/blog/posts/{slug}/comments", async (string slug, Comment comment, AppDbContext db) =>
|
||
{
|
||
var post = await db.BlogPosts.FirstOrDefaultAsync(p => p.Slug == slug && p.IsPublished);
|
||
if (post is null) return Results.NotFound();
|
||
comment.BlogPostId = post.Id;
|
||
comment.IsApproved = false;
|
||
comment.CreatedAt = DateTime.UtcNow;
|
||
// Basic sanitise: strip HTML tags from body
|
||
comment.Body = System.Text.RegularExpressions.Regex.Replace(comment.Body ?? "", "<[^>]*>", "").Trim();
|
||
comment.AuthorName = System.Text.RegularExpressions.Regex.Replace(comment.AuthorName ?? "", "<[^>]*>", "").Trim();
|
||
if (string.IsNullOrWhiteSpace(comment.Body) || string.IsNullOrWhiteSpace(comment.AuthorName))
|
||
return Results.BadRequest("نام و متن نظر الزامی است.");
|
||
db.Comments.Add(comment);
|
||
await db.SaveChangesAsync();
|
||
return Results.Ok(new { message = "نظر شما دریافت شد و پس از تأیید نمایش داده میشود." });
|
||
});
|
||
|
||
// Admin: list all comments (with post title)
|
||
app.MapGet("/api/comments", async (AppDbContext db) =>
|
||
Results.Ok(await db.Comments
|
||
.Include(c => c.Post)
|
||
.OrderByDescending(c => c.CreatedAt)
|
||
.Select(c => new {
|
||
c.Id, c.AuthorName, c.AuthorEmail, c.Body,
|
||
c.IsApproved, c.CreatedAt,
|
||
PostTitle = c.Post == null ? "" : c.Post.Title,
|
||
PostSlug = c.Post == null ? "" : c.Post.Slug
|
||
}).ToListAsync()))
|
||
.RequireAuthorization();
|
||
|
||
// Admin: approve
|
||
app.MapPut("/api/comments/{id:int}/approve", async (int id, AppDbContext db) =>
|
||
{
|
||
var c = await db.Comments.FindAsync(id);
|
||
if (c is null) return Results.NotFound();
|
||
c.IsApproved = true;
|
||
await db.SaveChangesAsync();
|
||
return Results.Ok(c);
|
||
}).RequireAuthorization();
|
||
|
||
// Admin: reply to a comment
|
||
app.MapPost("/api/comments/{id:int}/reply", async (int id, Comment reply, AppDbContext db) =>
|
||
{
|
||
var parent = await db.Comments.FindAsync(id);
|
||
if (parent is null) return Results.NotFound();
|
||
reply.ParentId = id;
|
||
reply.BlogPostId = parent.BlogPostId;
|
||
reply.AuthorName = "دکتر سوسن آلطه";
|
||
reply.IsAdminReply = true;
|
||
reply.IsApproved = true; // admin replies are auto-approved
|
||
reply.CreatedAt = DateTime.UtcNow;
|
||
reply.Body = System.Text.RegularExpressions.Regex.Replace(reply.Body ?? "", "<[^>]*>", "").Trim();
|
||
if (string.IsNullOrWhiteSpace(reply.Body)) return Results.BadRequest("متن پاسخ نمیتواند خالی باشد.");
|
||
db.Comments.Add(reply);
|
||
await db.SaveChangesAsync();
|
||
return Results.Ok(reply);
|
||
}).RequireAuthorization();
|
||
|
||
// Admin: delete
|
||
app.MapDelete("/api/comments/{id:int}", async (int id, AppDbContext db) =>
|
||
{
|
||
var c = await db.Comments.FindAsync(id);
|
||
if (c is null) return Results.NotFound();
|
||
db.Comments.Remove(c);
|
||
await db.SaveChangesAsync();
|
||
return Results.NoContent();
|
||
}).RequireAuthorization();
|
||
|
||
// ── File Upload ───────────────────────────────────────────────────────────────
|
||
app.MapPost("/api/upload", async (HttpRequest request, IWebHostEnvironment env) =>
|
||
{
|
||
if (!request.HasFormContentType || !request.Form.Files.Any())
|
||
return Results.BadRequest("No file provided.");
|
||
var file = request.Form.Files[0];
|
||
var allowed = new[] { ".jpg", ".jpeg", ".png", ".webp", ".gif", ".svg", ".ico" };
|
||
var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
|
||
if (!allowed.Contains(ext)) return Results.BadRequest("File type not allowed.");
|
||
var uploadsDir = Path.Combine(env.WebRootPath, "uploads");
|
||
Directory.CreateDirectory(uploadsDir);
|
||
var fileName = $"{Guid.NewGuid()}{ext}";
|
||
await using var stream = File.Create(Path.Combine(uploadsDir, fileName));
|
||
await file.CopyToAsync(stream);
|
||
return Results.Ok(new { url = $"/uploads/{fileName}" });
|
||
}).RequireAuthorization();
|
||
|
||
app.MapGet("/api/upload", (IWebHostEnvironment env) =>
|
||
{
|
||
var uploadsDir = Path.Combine(env.WebRootPath, "uploads");
|
||
if (!Directory.Exists(uploadsDir)) return Results.Ok(Array.Empty<object>());
|
||
var files = new DirectoryInfo(uploadsDir).GetFiles()
|
||
.OrderByDescending(f => f.LastWriteTime)
|
||
.Select(f => new { url = $"/uploads/{f.Name}", name = f.Name, size = f.Length, date = f.LastWriteTime.ToString("yyyy-MM-ddTHH:mm:ss") });
|
||
return Results.Ok(files);
|
||
}).RequireAuthorization();
|
||
|
||
app.MapDelete("/api/upload/{filename}", (string filename, IWebHostEnvironment env) =>
|
||
{
|
||
if (filename.Contains("..") || filename.Contains('/') || filename.Contains('\\'))
|
||
return Results.BadRequest("Invalid filename.");
|
||
var path = Path.Combine(env.WebRootPath, "uploads", filename);
|
||
if (!File.Exists(path)) return Results.NotFound();
|
||
File.Delete(path);
|
||
return Results.NoContent();
|
||
}).RequireAuthorization();
|
||
|
||
// ── Patients (admin) ──────────────────────────────────────────────────────────
|
||
var patientsGroup = app.MapGroup("/api/patients").RequireAuthorization();
|
||
|
||
patientsGroup.MapGet("/", async (string? category, string? search, AppDbContext db) =>
|
||
{
|
||
var q = db.Patients.Where(p => p.IsActive);
|
||
if (!string.IsNullOrEmpty(category)) q = q.Where(p => p.Category == category);
|
||
if (!string.IsNullOrEmpty(search))
|
||
q = q.Where(p => p.FullName.Contains(search) || p.PhoneNumber.Contains(search));
|
||
return Results.Ok(await q.OrderByDescending(p => p.CreatedAt)
|
||
.Select(p => new { p.Id, p.FullName, p.PhoneNumber, p.Email, p.Age, p.Gender,
|
||
p.Category, p.BloodType, p.CreatedAt,
|
||
VisitCount = db.PatientVisits.Count(v => v.PatientId == p.Id) })
|
||
.ToListAsync());
|
||
});
|
||
|
||
patientsGroup.MapGet("/{id:int}", async (int id, AppDbContext db) =>
|
||
{
|
||
var p = await db.Patients.Include(x => x.Visits.OrderByDescending(v => v.VisitDate))
|
||
.FirstOrDefaultAsync(x => x.Id == id);
|
||
return p is null ? Results.NotFound() : Results.Ok(p);
|
||
});
|
||
|
||
patientsGroup.MapPost("/", async (Patient patient, AppDbContext db) =>
|
||
{
|
||
patient.CreatedAt = DateTime.UtcNow;
|
||
db.Patients.Add(patient);
|
||
await db.SaveChangesAsync();
|
||
return Results.Created($"/api/patients/{patient.Id}", patient);
|
||
});
|
||
|
||
patientsGroup.MapPut("/{id:int}", async (int id, Patient updated, AppDbContext db) =>
|
||
{
|
||
var p = await db.Patients.FindAsync(id);
|
||
if (p is null) return Results.NotFound();
|
||
p.FullName = updated.FullName; p.PhoneNumber = updated.PhoneNumber;
|
||
p.Email = updated.Email; p.Age = updated.Age; p.Weight = updated.Weight;
|
||
p.Height = updated.Height; p.Gender = updated.Gender; p.BloodType = updated.BloodType;
|
||
p.DiseaseHistory = updated.DiseaseHistory; p.Allergies = updated.Allergies;
|
||
p.Medications = updated.Medications; p.Notes = updated.Notes;
|
||
p.Category = updated.Category; p.IsActive = updated.IsActive;
|
||
await db.SaveChangesAsync();
|
||
return Results.Ok(p);
|
||
});
|
||
|
||
patientsGroup.MapDelete("/{id:int}", async (int id, AppDbContext db) =>
|
||
{
|
||
var p = await db.Patients.FindAsync(id);
|
||
if (p is null) return Results.NotFound();
|
||
p.IsActive = false; // soft delete
|
||
await db.SaveChangesAsync();
|
||
return Results.NoContent();
|
||
});
|
||
|
||
// Patient visits/notes
|
||
patientsGroup.MapGet("/{id:int}/visits", async (int id, AppDbContext db) =>
|
||
Results.Ok(await db.PatientVisits.Where(v => v.PatientId == id)
|
||
.OrderByDescending(v => v.VisitDate).ToListAsync()));
|
||
|
||
patientsGroup.MapPost("/{id:int}/visits", async (int id, PatientVisit visit, AppDbContext db) =>
|
||
{
|
||
var patient = await db.Patients.FindAsync(id);
|
||
if (patient is null) return Results.NotFound();
|
||
visit.PatientId = id;
|
||
visit.CreatedAt = DateTime.UtcNow;
|
||
if (visit.VisitDate == default) visit.VisitDate = DateTime.UtcNow;
|
||
db.PatientVisits.Add(visit);
|
||
await db.SaveChangesAsync();
|
||
return Results.Created($"/api/patients/{id}/visits/{visit.Id}", visit);
|
||
});
|
||
|
||
patientsGroup.MapDelete("/visits/{visitId:int}", async (int visitId, AppDbContext db) =>
|
||
{
|
||
var v = await db.PatientVisits.FindAsync(visitId);
|
||
if (v is null) return Results.NotFound();
|
||
db.PatientVisits.Remove(v);
|
||
await db.SaveChangesAsync();
|
||
return Results.NoContent();
|
||
});
|
||
|
||
// ── Health Requests (public submit / admin manage) ────────────────────────────
|
||
app.MapPost("/api/health-request", async (HealthRequest req, AppDbContext db) =>
|
||
{
|
||
req.CreatedAt = DateTime.UtcNow;
|
||
req.IsHandled = false;
|
||
req.TrackingCode = "DR-" + GenerateTrackingCode();
|
||
db.HealthRequests.Add(req);
|
||
await db.SaveChangesAsync();
|
||
return Results.Ok(new { message = "درخواست شما ثبت شد", trackingCode = req.TrackingCode, id = req.Id });
|
||
});
|
||
|
||
// Public: look up own request by tracking code
|
||
app.MapGet("/api/health-request/track/{code}", async (string code, AppDbContext db) =>
|
||
{
|
||
var r = await db.HealthRequests.FirstOrDefaultAsync(x => x.TrackingCode == code);
|
||
if (r is null) return Results.NotFound(new { message = "کد رهگیری یافت نشد" });
|
||
return Results.Ok(new {
|
||
r.TrackingCode, r.FullName, r.Category, r.Message, r.IsHandled,
|
||
r.Diagnosis, r.DoctorReply, r.RepliedAt, r.CreatedAt
|
||
});
|
||
});
|
||
|
||
app.MapGet("/api/health-requests", async (bool? handled, string? phone, AppDbContext db) =>
|
||
{
|
||
var q = db.HealthRequests.AsQueryable();
|
||
if (handled.HasValue) q = q.Where(r => r.IsHandled == handled);
|
||
if (!string.IsNullOrEmpty(phone)) q = q.Where(r => r.PhoneNumber == phone);
|
||
return Results.Ok(await q.OrderByDescending(r => r.CreatedAt).ToListAsync());
|
||
}).RequireAuthorization();
|
||
|
||
// Doctor reply: set diagnosis + reply text + mark handled
|
||
app.MapPut("/api/health-requests/{id:int}/reply", async (int id, DoctorReplyDto dto, AppDbContext db) =>
|
||
{
|
||
var r = await db.HealthRequests.FindAsync(id);
|
||
if (r is null) return Results.NotFound();
|
||
r.Diagnosis = dto.Diagnosis ?? r.Diagnosis;
|
||
r.DoctorReply = dto.DoctorReply ?? r.DoctorReply;
|
||
r.IsHandled = true;
|
||
r.RepliedAt = DateTime.UtcNow;
|
||
await db.SaveChangesAsync();
|
||
return Results.Ok(r);
|
||
}).RequireAuthorization();
|
||
|
||
// Mark handled without reply
|
||
app.MapPut("/api/health-requests/{id:int}", async (int id, AppDbContext db) =>
|
||
{
|
||
var r = await db.HealthRequests.FindAsync(id);
|
||
if (r is null) return Results.NotFound();
|
||
r.IsHandled = true;
|
||
await db.SaveChangesAsync();
|
||
return Results.Ok(r);
|
||
}).RequireAuthorization();
|
||
|
||
app.MapDelete("/api/health-requests/{id:int}", async (int id, AppDbContext db) =>
|
||
{
|
||
var r = await db.HealthRequests.FindAsync(id);
|
||
if (r is null) return Results.NotFound();
|
||
db.HealthRequests.Remove(r);
|
||
await db.SaveChangesAsync();
|
||
return Results.NoContent();
|
||
}).RequireAuthorization();
|
||
|
||
// ── Sitemap ───────────────────────────────────────────────────────────────────
|
||
app.MapGet("/sitemap.xml", async (AppDbContext db, HttpContext ctx) =>
|
||
{
|
||
// SITE_BASE_URL env var wins (e.g. "https://draletaha.ir") — falls back to request scheme+host
|
||
var baseUrl = Environment.GetEnvironmentVariable("SITE_BASE_URL")?.TrimEnd('/')
|
||
?? $"{ctx.Request.Scheme}://{ctx.Request.Host}";
|
||
var published = await db.BlogPosts.Where(p => p.IsPublished)
|
||
.Select(p => new { p.Slug, p.UpdatedAt }).ToListAsync();
|
||
|
||
var sb = new StringBuilder();
|
||
sb.AppendLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||
sb.AppendLine("<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">");
|
||
|
||
void Url(string loc, string priority, string freq, DateTime? lm = null)
|
||
{
|
||
sb.AppendLine(" <url>");
|
||
sb.AppendLine($" <loc>{loc}</loc>");
|
||
if (lm.HasValue) sb.AppendLine($" <lastmod>{lm.Value:yyyy-MM-dd}</lastmod>");
|
||
sb.AppendLine($" <changefreq>{freq}</changefreq>");
|
||
sb.AppendLine($" <priority>{priority}</priority>");
|
||
sb.AppendLine(" </url>");
|
||
}
|
||
|
||
Url(baseUrl + "/", "1.0", "weekly", DateTime.UtcNow);
|
||
Url(baseUrl + "/gallery", "0.8", "weekly", DateTime.UtcNow);
|
||
Url(baseUrl + "/blog", "0.9", "daily", DateTime.UtcNow);
|
||
foreach (var p in published)
|
||
Url($"{baseUrl}/blog/{p.Slug}", "0.8", "monthly", p.UpdatedAt);
|
||
|
||
sb.AppendLine("</urlset>");
|
||
ctx.Response.ContentType = "application/xml; charset=utf-8";
|
||
await ctx.Response.WriteAsync(sb.ToString());
|
||
});
|
||
|
||
// ── Health Check ─────────────────────────────────────────────────────────────
|
||
app.MapGet("/healthz", () => Results.Ok(new { status = "healthy", utc = DateTime.UtcNow }));
|
||
|
||
// ── Robots.txt ────────────────────────────────────────────────────────────────
|
||
app.MapGet("/robots.txt", (HttpContext ctx) =>
|
||
{
|
||
var host = Environment.GetEnvironmentVariable("SITE_BASE_URL")?.TrimEnd('/')
|
||
?? $"{ctx.Request.Scheme}://{ctx.Request.Host}";
|
||
var body = $"User-agent: *\nAllow: /\nDisallow: /admin/\nDisallow: /api/\n\nSitemap: {host}/sitemap.xml";
|
||
ctx.Response.ContentType = "text/plain";
|
||
return ctx.Response.WriteAsync(body);
|
||
});
|
||
|
||
// ── SEO Stats ─────────────────────────────────────────────────────────────────
|
||
app.MapGet("/api/seo/stats", async (AppDbContext db) =>
|
||
{
|
||
var total = await db.BlogPosts.CountAsync(p => p.IsPublished);
|
||
var views = await db.BlogPosts.SumAsync(p => (int?)p.ViewCount) ?? 0;
|
||
var topPosts = await db.BlogPosts.Where(p => p.IsPublished)
|
||
.OrderByDescending(p => p.ViewCount).Take(5)
|
||
.Select(p => new { p.Title, p.Slug, p.ViewCount }).ToListAsync();
|
||
var noMeta = await db.BlogPosts.CountAsync(p => p.IsPublished &&
|
||
(p.MetaDescription == "" || p.FocusKeyword == ""));
|
||
return Results.Ok(new { total, views, topPosts, noMeta });
|
||
}).RequireAuthorization();
|
||
|
||
// Generic error page — returns 500 with a minimal HTML body so Googlebot
|
||
// gets a proper HTTP 500 (not a connection-reset) and retries cleanly.
|
||
app.Map("/error", (HttpContext ctx) =>
|
||
{
|
||
ctx.Response.StatusCode = 500;
|
||
ctx.Response.ContentType = "text/html; charset=utf-8";
|
||
return ctx.Response.WriteAsync(
|
||
"<!DOCTYPE html><html lang='fa'><head><meta charset='utf-8'>" +
|
||
"<title>خطای سرور</title></head><body dir='rtl'>" +
|
||
"<h1>خطای موقت سرور</h1>" +
|
||
"<p>مشکلی پیش آمده. لطفاً دقایقی دیگر مجدداً تلاش کنید.</p>" +
|
||
"</body></html>");
|
||
});
|
||
|
||
app.MapRazorPages();
|
||
app.Run();
|
||
return 0;
|
||
|
||
// ══════════════════════════════════════════════════════════════════════════════
|
||
// HELPERS
|
||
// ══════════════════════════════════════════════════════════════════════════════
|
||
static string Slugify(string text)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(text)) return Guid.NewGuid().ToString("N")[..8];
|
||
text = text.Trim().ToLowerInvariant();
|
||
text = System.Text.RegularExpressions.Regex.Replace(text, @"[\s_]+", "-");
|
||
text = System.Text.RegularExpressions.Regex.Replace(text, @"[^\p{L}\p{N}\-]", "");
|
||
text = System.Text.RegularExpressions.Regex.Replace(text, @"-{2,}", "-");
|
||
return text.Trim('-');
|
||
}
|
||
|
||
static string GenerateTrackingCode()
|
||
{
|
||
const string chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||
var rng = System.Security.Cryptography.RandomNumberGenerator.Create();
|
||
var bytes = new byte[6];
|
||
rng.GetBytes(bytes);
|
||
return new string(bytes.Select(b => chars[b % chars.Length]).ToArray());
|
||
}
|
||
|
||
static int EstimateReadingTime(string content)
|
||
{
|
||
if (string.IsNullOrEmpty(content)) return 1;
|
||
var words = content.Split([' ', '\n', '\r', '\t'], StringSplitOptions.RemoveEmptyEntries).Length;
|
||
return Math.Max(1, (int)Math.Ceiling(words / 200.0));
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════════════════════════
|
||
// SEED DATA (runs once, skipped if tables already have rows)
|
||
// ══════════════════════════════════════════════════════════════════════════════
|
||
static async Task SeedAsync(AppDbContext db)
|
||
{
|
||
// ── Site Settings ─────────────────────────────────────────────────────────
|
||
if (!await db.SiteSettings.AnyAsync())
|
||
{
|
||
db.SiteSettings.AddRange(
|
||
// Hero
|
||
new() { Section="hero", Key="name", Value="دکتر سوسن آلطه" },
|
||
new() { Section="hero", Key="title", Value="پزشک عمومی و متخصص زیبایی پوست" },
|
||
new() { Section="hero", Key="subtitle", Value="با بیش از یک دهه تجربه در حوزهی زیبایی و مراقبت از پوست، زیبایی واقعی شما را با علم و هنر همراه میکنیم." },
|
||
new() { Section="hero", Key="stat1_num", Value="+۱۰" },
|
||
new() { Section="hero", Key="stat1_lbl", Value="سال تجربه" },
|
||
new() { Section="hero", Key="stat2_num", Value="+۵۰۰" },
|
||
new() { Section="hero", Key="stat2_lbl", Value="بیمار راضی" },
|
||
new() { Section="hero", Key="stat3_num", Value="+۱۵" },
|
||
new() { Section="hero", Key="stat3_lbl", Value="خدمات تخصصی" },
|
||
new() { Section="hero", Key="cta_primary", Value="رزرو نوبت" },
|
||
new() { Section="hero", Key="cta_secondary", Value="بیشتر بدانید" },
|
||
// About
|
||
new() { Section="about", Key="title", Value="سلامت و زیبایی پوست هدف اصلی من است" },
|
||
new() { Section="about", Key="bio", Value="دکتر سوسن آلطه، پزشک عمومی و متخصص زیبایی پوست با سالها تجربه در ارائه خدمات پیشرفته پوست و زیبایی. ایشان با ترکیب دانش پزشکی و هنر زیبایی، بهترین نتایج را برای بیماران خود رقم میزنند." },
|
||
new() { Section="about", Key="years_exp", Value="۱۰+" },
|
||
new() { Section="about", Key="cred1", Value="دکترای پزشکی عمومی از دانشگاه ایران" },
|
||
new() { Section="about", Key="cred2", Value="تخصص در حوزه زیبایی و مراقبت از پوست" },
|
||
new() { Section="about", Key="cred3", Value="آشنا با جدیدترین روشهای زیبایی روز دنیا" },
|
||
new() { Section="about", Key="cred4", Value="عضو انجمنهای تخصصی پزشکی ایران" },
|
||
new() { Section="about", Key="cred5", Value="مشاوره و درمان شخصیسازیشده برای هر بیمار" },
|
||
// Contact
|
||
new() { Section="contact", Key="phone", Value="۰۲۱-XXXX-XXXX" },
|
||
new() { Section="contact", Key="email", Value="dr.sousan@example.com" },
|
||
new() { Section="contact", Key="address", Value="تهران، خیابان ولیعصر، ساختمان پزشکی" },
|
||
new() { Section="contact", Key="hours", Value="شنبه تا چهارشنبه: ۹ تا ۱۸ | پنجشنبه: ۹ تا ۱۴" },
|
||
new() { Section="contact", Key="instagram", Value="#" },
|
||
new() { Section="contact", Key="whatsapp", Value="#" },
|
||
new() { Section="contact", Key="telegram", Value="#" },
|
||
new() { Section="contact", Key="bale", Value="#" },
|
||
new() { Section="contact", Key="rubika", Value="#" }
|
||
);
|
||
await db.SaveChangesAsync();
|
||
}
|
||
|
||
// ── Services ──────────────────────────────────────────────────────────────
|
||
if (!await db.Services.AnyAsync())
|
||
{
|
||
db.Services.AddRange(
|
||
new Service { Title="مراقبت و درمان پوست", Description="ارزیابی کامل پوست و تجویز برنامه درمانی مناسب برای انواع مشکلات پوستی شامل آکنه، لک و چین و چروک.", Order=1 },
|
||
new Service { Title="بوتاکس و فیلر", Description="تزریق بوتاکس و فیلر با بالاترین کیفیت برای جوانسازی چهره و رفع چین و چروکهای عمیق.", Order=2 },
|
||
new Service { Title="لیزر درمانی", Description="درمان با پیشرفتهترین دستگاههای لیزر برای رفع موهای زائد، لک و ضایعات پوستی.", Order=3 },
|
||
new Service { Title="مزوتراپی", Description="تزریق ویتامینها و مواد مغذی مستقیم به پوست برای درمان ریزش مو و جوانسازی پوست.", Order=4 },
|
||
new Service { Title="پاکسازی عمیق پوست", Description="پاکسازی حرفهای پوست با روشهای تخصصی برای رفع جوشهای سرسیاه و سرسفید.", Order=5 },
|
||
new Service { Title="مشاوره زیبایی", Description="مشاوره تخصصی و برنامهریزی شخصیسازیشده برای مراقبت از پوست و رسیدن به اهداف زیبایی شما.", Order=6 },
|
||
new Service { Title="لیزر بیکینی", Description="حذف دائمی موهای ناحیه بیکینی با دستگاههای پیشرفته لیزر دیود. بدون درد، بدون عوارض، با حداکثر راحتی و بهداشت.", Order=7 },
|
||
new Service { Title="فیلر بدن", Description="تزریق فیلر برای رفع فرورفتگیها، اسکارها و اصلاح فرم بدن. روشی غیرجراحی برای بهبود ظاهر و افزایش اعتماد به نفس.", Order=8 },
|
||
new Service { Title="هایفو (HIFU)", Description="لیفتینگ غیرجراحی صورت با فناوری اولتراسوند متمرکز. سفتشدن پوست، کاهش چربی موضعی و لیفت ماندگار بدون جراحی.", Order=9 },
|
||
new Service { Title="RF لیفتینگ", Description="جوانسازی و سفتشدن پوست با رادیوفرکوئنسی. محرک تولید کلاژن طبیعی برای پوستی شفاف، جوانتر و سفتتر.", Order=10 }
|
||
);
|
||
await db.SaveChangesAsync();
|
||
}
|
||
|
||
// ── Testimonials ──────────────────────────────────────────────────────────
|
||
if (!await db.Testimonials.AnyAsync())
|
||
{
|
||
db.Testimonials.AddRange(
|
||
new Testimonial { AuthorName="سارا محمدی", AuthorEmoji="👩", Rating=5, Date="اردیبهشت ۱۴۰۳", Text="دکتر آلطه با صبر و دقت زیاد مشکل پوستی من را بررسی کردند و درمان دقیقی تجویز کردند. نتیجه فوقالعاده بود. ممنونم دکتر." },
|
||
new Testimonial { AuthorName="مریم حسینی", AuthorEmoji="👩", Rating=5, Date="فروردین ۱۴۰۳", Text="بعد از تزریق بوتاکس توسط دکتر آلطه، نتیجهای طبیعی و زیبا داشتم که همه اطرافیانم تعریف کردند. حتما توصیه میکنم." },
|
||
new Testimonial { AuthorName="نیلوفر رضایی", AuthorEmoji="👩", Rating=5, Date="اسفند ۱۴۰۲", Text="دکتر خیلی حرفهای و دلسوز هستند. بعد از چند جلسه لیزر، پوستم کاملا صاف شد. از اعتماد و تخصصشان ممنونم." }
|
||
);
|
||
await db.SaveChangesAsync();
|
||
}
|
||
|
||
// ── Blog categories ───────────────────────────────────────────────────────
|
||
if (!await db.BlogCategories.AnyAsync())
|
||
{
|
||
db.BlogCategories.AddRange(
|
||
new BlogCategory { Name="زیبایی پوست", Slug="zibaei-poost", Description="مقالات تخصصی درباره روشهای زیبایی پوست" },
|
||
new BlogCategory { Name="مراقبت از پوست", Slug="moraghebet-poost", Description="راهنمای مراقبت روزانه از پوست" },
|
||
new BlogCategory { Name="درمانهای تخصصی", Slug="darman-takhasosi", Description="روشهای پیشرفته درمان مشکلات پوستی" },
|
||
new BlogCategory { Name="آموزش و راهنما", Slug="amozesh", Description="آموزشهای کاربردی مراقبت از پوست" }
|
||
);
|
||
await db.SaveChangesAsync();
|
||
}
|
||
|
||
// ── Blog posts ────────────────────────────────────────────────────────────
|
||
if (!await db.BlogPosts.AnyAsync())
|
||
{
|
||
var cat1 = await db.BlogCategories.FirstAsync(c => c.Slug == "zibaei-poost");
|
||
var cat2 = await db.BlogCategories.FirstAsync(c => c.Slug == "moraghebet-poost");
|
||
var cat3 = await db.BlogCategories.FirstAsync(c => c.Slug == "darman-takhasosi");
|
||
|
||
db.BlogPosts.AddRange(
|
||
|
||
new BlogPost {
|
||
Title="بوتاکس چیست؟ راهنمای کامل تزریق بوتاکس صورت", Slug="botox-chist",
|
||
Excerpt="بوتاکس یکی از محبوبترین روشهای غیرجراحی زیبایی است. در این مقاله همه چیز درباره بوتاکس، کاربردها، مزایا، معایب و نکات مهم قبل و بعد از تزریق را بیاموزید.",
|
||
FocusKeyword="بوتاکس", Keywords="بوتاکس,تزریق بوتاکس,بوتاکس صورت,بوتاکس چیست,جوانسازی پوست,چین و چروک",
|
||
MetaTitle="بوتاکس چیست؟ راهنمای کامل تزریق بوتاکس | دکتر سوسن آلطه",
|
||
MetaDescription="همه چیز درباره بوتاکس: کاربردها، مزایا، معایب، هزینه و نکات قبل و بعد از تزریق. راهنمای جامع دکتر سوسن آلطه متخصص زیبایی پوست.",
|
||
ArticleType="MedicalWebPage", IsPublished=true, PublishedAt=DateTime.UtcNow.AddDays(-30),
|
||
CategoryId=cat1.Id, ReadingTimeMinutes=7,
|
||
Content=@"<h2>بوتاکس چیست؟</h2>
|
||
<p>بوتاکس یا بوتولینوم توکسین نوع A، یک ماده بیولوژیک است که از باکتری <em>Clostridium botulinum</em> به دست میآید. این ماده به صورت کنترلشده و در مقادیر بسیار کم برای اهداف پزشکی و زیبایی مورد استفاده قرار میگیرد. بوتاکس با مسدود کردن سیگنالهای عصبی به عضلات، موجب استراحت موقت آنها میشود و بدین ترتیب چین و چروکهای ناشی از حرکات تکراری چهره را کاهش میدهد.</p>
|
||
<h2>کاربردهای بوتاکس در زیبایی پوست</h2>
|
||
<ul><li><strong>خطوط پیشانی:</strong> رفع خطوط افقی پیشانی که با بالا رفتن ابروها ایجاد میشوند.</li><li><strong>خطوط بین ابرو (گلابلا):</strong> خطوط عمودی که با اخم ایجاد میشوند.</li><li><strong>دور چشم (پاهای کلاغ):</strong> چینهای ظریف کنار چشم با خندیدن.</li><li><strong>لیفت ابرو:</strong> بالا آوردن ابروها برای باز شدن چشمها.</li><li><strong>درمان تعریق بیش از حد:</strong> کاهش تعریق زیر بغل، کف دست و پا.</li></ul>
|
||
<h2>مزایای تزریق بوتاکس</h2>
|
||
<ul><li>روش غیرجراحی با حداقل زمان بهبودی</li><li>نتایج سریع (ظرف ۳ تا ۷ روز)</li><li>اثر طبیعی بدون تغییر بیش از حد در ظاهر</li><li>قابل برگشت بودن اثرات</li><li>مناسب برای پیشگیری از چین و چروکهای عمیق</li></ul>
|
||
<h2>نکات مهم قبل از تزریق بوتاکس</h2>
|
||
<ol><li>حداقل ۲ هفته قبل از تزریق، مصرف داروهای رقیقکننده خون را قطع کنید.</li><li>از مصرف الکل ۲۴ ساعت قبل از تزریق خودداری کنید.</li><li>در صورت داشتن عفونت پوستی، تزریق را به تعویق بیندازید.</li><li>بارداری و شیردهی از موارد منع تزریق است.</li></ol>
|
||
<h2>مراقبتهای بعد از تزریق بوتاکس</h2>
|
||
<ul><li>برای ۴ ساعت اول دراز نکشید.</li><li>از فعالیت ورزشی سنگین در روز تزریق خودداری کنید.</li><li>از ماساژ ناحیه تزریقشده بپرهیزید.</li><li>تا ۲۴ ساعت از گرمای شدید دوری کنید.</li></ul>
|
||
<h2>ماندگاری اثرات بوتاکس</h2>
|
||
<p>اثرات بوتاکس معمولاً بین ۳ تا ۶ ماه پایدار است. با تکرار منظم تزریقها، ماندگاری اثر بیشتر میشود زیرا عضلات به تدریج ضعیفتر میشوند.</p>
|
||
<h2>نتیجهگیری</h2>
|
||
<p>بوتاکس یکی از امنترین و موثرترین روشهای غیرجراحی جوانسازی پوست است. برای دریافت مشاوره رایگان همین امروز با مطب دکتر سوسن آلطه تماس بگیرید.</p>"
|
||
},
|
||
|
||
new BlogPost {
|
||
Title="درمان آکنه: بهترین روشهای علمی و تخصصی برای پوست بدون جوش", Slug="darman-akne",
|
||
Excerpt="آکنه شایعترین مشکل پوستی در ایران است. در این مقاله روشهای علمی و تخصصی درمان آکنه، انواع آن و نحوه پیشگیری را بیاموزید.",
|
||
FocusKeyword="درمان آکنه", Keywords="درمان آکنه,آکنه,جوش,جوش صورت,درمان جوش,آکنه کیستیک,پوست چرب",
|
||
MetaTitle="درمان آکنه | بهترین روشهای علمی | دکتر سوسن آلطه",
|
||
MetaDescription="بهترین روشهای علمی برای درمان آکنه و جوش صورت. از درمانهای موضعی تا لیزر درمانی. راهنمای تخصصی دکتر آلطه.",
|
||
ArticleType="MedicalWebPage", IsPublished=true, PublishedAt=DateTime.UtcNow.AddDays(-20),
|
||
CategoryId=cat3.Id, ReadingTimeMinutes=8,
|
||
Content=@"<h2>آکنه چیست و چرا ایجاد میشود؟</h2>
|
||
<p>آکنه یک بیماری مزمن پوستی است که در اثر مسدود شدن فولیکولهای مو توسط چربی و سلولهای مرده پوست ایجاد میشود. این مشکل معمولاً با رشد باکتریهای خاص (P. acnes) بدتر میشود.</p>
|
||
<h2>انواع آکنه</h2>
|
||
<ul><li><strong>جوشهای سرسفید:</strong> فولیکولهای مسدود زیر پوست</li><li><strong>جوشهای سرسیاه:</strong> فولیکولهای باز که در تماس با هوا تیره میشوند</li><li><strong>پاپولها:</strong> برجستگیهای قرمز کوچک</li><li><strong>پوسچولها:</strong> جوشهای چرکی</li><li><strong>کیست:</strong> شدیدترین نوع که میتواند اسکار ایجاد کند</li></ul>
|
||
<h2>روشهای درمان آکنه</h2>
|
||
<h3>۱. درمانهای موضعی</h3>
|
||
<ul><li><strong>بنزوئیل پراکسید:</strong> ضدباکتری قوی که کمدونها را کاهش میدهد</li><li><strong>رتینوئیدها (ترتینوئین):</strong> ویتامین A که عملکرد فولیکولها را تنظیم میکند</li><li><strong>آنتیبیوتیکهای موضعی:</strong> کلیندامایسین و اریترومایسین</li></ul>
|
||
<h3>۲. پروسیجرهای تخصصی در مطب</h3>
|
||
<ul><li><strong>پیلینگ شیمیایی:</strong> لایهبرداری شیمیایی برای باز کردن منافذ</li><li><strong>لیزر و نور درمانی:</strong> LED آبی برای کشتن باکتریها</li><li><strong>میکرودرمابریژن:</strong> لایهبرداری مکانیکی</li></ul>
|
||
<h2>مراقبت روزانه برای پوست مستعد آکنه</h2>
|
||
<ol><li>روزانه دو بار صورت را با شوینده ملایم بشویید</li><li>از لمس کردن صورت با دستهای آلوده بپرهیزید</li><li>کرم ضدآفتاب بدون چربی استفاده کنید</li><li>موهای چرب را دور از صورت نگه دارید</li></ol>
|
||
<h2>نتیجهگیری</h2>
|
||
<p>آکنه یک بیماری قابل درمان است. با تشخیص صحیح و برنامه درمانی مناسب، میتوان به پوست صاف دست یافت. با دکتر سوسن آلطه مشاوره بگیرید.</p>"
|
||
},
|
||
|
||
new BlogPost {
|
||
Title="لیزر موهای زائد: راهنمای کامل، مزایا و نکات مهم", Slug="laser-mohaey-zaed",
|
||
Excerpt="لیزر موهای زائد یکی از موثرترین روشهای دائمی حذف مو است. همه چیز درباره لیزر موهای زائد، انواع دستگاهها، تعداد جلسات و مراقبتهای لازم.",
|
||
FocusKeyword="لیزر موهای زائد", Keywords="لیزر موهای زائد,لیزر مو,حذف موهای زائد,لیزر الکساندریت,لیزر دیود,اپیلاسیون لیزر",
|
||
MetaTitle="لیزر موهای زائد | راهنمای کامل | دکتر سوسن آلطه",
|
||
MetaDescription="راهنمای کامل لیزر موهای زائد: انواع دستگاهها، تعداد جلسات، مراقبتها. متخصص زیبایی دکتر سوسن آلطه تهران.",
|
||
ArticleType="MedicalWebPage", IsPublished=true, PublishedAt=DateTime.UtcNow.AddDays(-15),
|
||
CategoryId=cat3.Id, ReadingTimeMinutes=6,
|
||
Content=@"<h2>چرا لیزر موهای زائد؟</h2>
|
||
<p>لیزر موهای زائد یکی از پیشرفتهترین و موثرترین روشهای حذف موهای ناخواسته است. برخلاف روشهای موقت مانند مومکاری یا تیغ، لیزر نتایج ماندگارتری ارائه میدهد.</p>
|
||
<h2>چگونه لیزر موها را حذف میکند؟</h2>
|
||
<p>لیزر موهای زائد از اصل انتخابی بودن انرژی نوری استفاده میکند. اشعه لیزر توسط رنگدانه ملانین در ریشه مو جذب شده و گرمای ایجاد شده، فولیکول مو را از بین میبرد.</p>
|
||
<h2>انواع لیزر موهای زائد</h2>
|
||
<ul><li><strong>لیزر الکساندریت (755nm):</strong> مناسب برای پوستهای روشن</li><li><strong>لیزر دیود (808nm):</strong> تطبیقپذیرترین نوع برای طیف وسیع پوستها</li><li><strong>لیزر Nd:YAG (1064nm):</strong> مناسب برای پوستهای تیرهتر</li></ul>
|
||
<h2>تعداد جلسات مورد نیاز</h2>
|
||
<p>برای نتیجه مطلوب به طور معمول ۶ تا ۸ جلسه نیاز است. فاصله بین جلسات بسته به ناحیه متفاوت است: صورت هر ۴-۶ هفته، بدن هر ۶-۸ هفته.</p>
|
||
<h2>مراقبتهای قبل از لیزر</h2>
|
||
<ol><li>۴ هفته قبل از مومکاری خودداری کنید</li><li>۲ هفته قبل از آفتاب شدید دوری کنید</li><li>در روز لیزر ناحیه را اصلاح کنید</li></ol>
|
||
<h2>نتیجهگیری</h2>
|
||
<p>لیزر موهای زائد یک سرمایهگذاری بلندمدت است. با انتخاب دستگاه مناسب و پزشک مجرب مانند دکتر سوسن آلطه، نتایج فوقالعادهای خواهید داشت.</p>"
|
||
},
|
||
|
||
new BlogPost {
|
||
Title="مزوتراپی پوست: روش طلایی جوانسازی و درمان ریزش مو", Slug="mezotarapi-poost",
|
||
Excerpt="مزوتراپی یک روش غیرجراحی است که با تزریق مستقیم ویتامینها و مواد مغذی به پوست، جوانسازی را سرعت میبخشد.",
|
||
FocusKeyword="مزوتراپی پوست", Keywords="مزوتراپی,مزوتراپی پوست,مزوتراپی مو,ریزش مو,جوانسازی پوست,کوکتل مزوتراپی",
|
||
MetaTitle="مزوتراپی پوست و مو | راهنمای کامل | دکتر سوسن آلطه",
|
||
MetaDescription="مزوتراپی پوست و مو چیست؟ مزایا، تعداد جلسات و نتایج. راهنمای کامل از دکتر سوسن آلطه متخصص زیبایی پوست تهران.",
|
||
ArticleType="MedicalWebPage", IsPublished=true, PublishedAt=DateTime.UtcNow.AddDays(-10),
|
||
CategoryId=cat1.Id, ReadingTimeMinutes=6,
|
||
Content=@"<h2>مزوتراپی چیست؟</h2>
|
||
<p>مزوتراپی یک تکنیک پزشکی-زیبایی است که در دهه ۱۹۵۰ توسط دکتر میشل پیستور ابداع شد. در این روش، مقادیر کوچکی از ترکیبات دارویی، ویتامینها، آمینواسیدها و اسید هیالورونیک مستقیماً به لایه میانی پوست تزریق میشوند.</p>
|
||
<h2>کاربردهای مزوتراپی</h2>
|
||
<h3>مزوتراپی صورت</h3>
|
||
<ul><li>جوانسازی و تروفیسم پوست</li><li>رفع خشکی و کمآبی پوست</li><li>کاهش لکها</li><li>بهبود بافت و درخشش پوست</li></ul>
|
||
<h3>مزوتراپی مو (ضد ریزش)</h3>
|
||
<ul><li>درمان ریزش موی آندروژنیک</li><li>تقویت فولیکولهای مو</li><li>بهبود گردش خون پوست سر</li></ul>
|
||
<h2>ترکیبات رایج کوکتل مزوتراپی</h2>
|
||
<ul><li><strong>اسید هیالورونیک:</strong> آبرسانی عمیق</li><li><strong>ویتامین C:</strong> آنتیاکسیدان قوی و روشنکننده</li><li><strong>بیوتین:</strong> ضروری برای رشد مو</li><li><strong>پپتیدها:</strong> تحریک ساخت کلاژن</li></ul>
|
||
<h2>تعداد جلسات</h2>
|
||
<p>برای نتایج مطلوب معمولاً ۴ تا ۶ جلسه با فاصله ۱ تا ۲ هفته نیاز است.</p>
|
||
<h2>نتیجهگیری</h2>
|
||
<p>مزوتراپی یکی از موثرترین روشهای غیرجراحی برای جوانسازی پوست است. با مشاوره دکتر سوسن آلطه بهترین پروتکل درمانی را دریافت کنید.</p>"
|
||
},
|
||
|
||
new BlogPost {
|
||
Title="مراقبت از پوست در تابستان: ۱۰ نکته طلایی از متخصص پوست", Slug="moraghebet-poost-tabestan",
|
||
Excerpt="آفتاب تابستان پوست را آسیب میزند اما با رعایت نکات ساده میتوان از پوست محافظت کرد. ۱۰ نکته طلایی مراقبت از پوست در تابستان.",
|
||
FocusKeyword="مراقبت از پوست در تابستان", Keywords="مراقبت از پوست,پوست در تابستان,ضدآفتاب,SPF,کرم ضدآفتاب,مراقبت پوست",
|
||
MetaTitle="مراقبت از پوست در تابستان | ۱۰ نکته طلایی | دکتر آلطه",
|
||
MetaDescription="۱۰ نکته طلایی مراقبت از پوست در تابستان از دکتر سوسن آلطه. آشنایی با SPF، ضدآفتاب مناسب و روتین مراقبت از پوست در گرما.",
|
||
ArticleType="Article", IsPublished=true, PublishedAt=DateTime.UtcNow.AddDays(-5),
|
||
CategoryId=cat2.Id, ReadingTimeMinutes=5,
|
||
Content=@"<h2>چرا مراقبت از پوست در تابستان اهمیت دارد؟</h2>
|
||
<p>اشعه UV خورشید یکی از اصلیترین عوامل پیری زودرس پوست، لک، چین و چروک و حتی سرطان پوست است. در تابستان که شدت تابش خورشید به اوج میرسد، مراقبت ویژه ضروری است.</p>
|
||
<h2>۱۰ نکته طلایی</h2>
|
||
<h3>۱. ضدآفتاب را جدی بگیرید</h3>
|
||
<p>از ضدآفتاب با SPF حداقل ۳۰ (ترجیحاً ۵۰) هر ۲ ساعت یکبار استفاده کنید.</p>
|
||
<h3>۲. پاکسازی دو بار در روز</h3>
|
||
<p>با توجه به افزایش تعریق در تابستان، پاکسازی دو بار در روز ضروری است.</p>
|
||
<h3>۳. آبرسانی کافی پوست</h3>
|
||
<p>از مرطوبکنندههای سبک و غیرچرب استفاده کنید. محصولات حاوی اسید هیالورونیک مناسب هستند.</p>
|
||
<h3>۴. آب بنوشید</h3>
|
||
<p>روزانه حداقل ۸ لیوان آب بنوشید.</p>
|
||
<h3>۵. از آفتاب مستقیم در اوج گرما پرهیز کنید</h3>
|
||
<p>بین ساعت ۱۰ صبح تا ۴ بعدازظهر از بیرون رفتن بپرهیزید.</p>
|
||
<h3>۶. لباس مناسب بپوشید</h3>
|
||
<p>لباسهای سبک رنگ با آستین بلند و کلاه لبهدار از پوست محافظت میکنند.</p>
|
||
<h3>۷. روتین شب را فراموش نکنید</h3>
|
||
<p>از محصولات حاوی رتینول، ویتامین C در شب استفاده کنید.</p>
|
||
<h3>۸. لایهبرداری ملایم</h3>
|
||
<p>لایهبرداری هفتگی با اسیدهای ملایم مانند AHA و BHA مفید است.</p>
|
||
<h3>۹. سرمادهی به پوست</h3>
|
||
<p>نگهداری مرطوبکننده در یخچال میتواند التهاب پوست را کاهش دهد.</p>
|
||
<h3>۱۰. رژیم غذایی آنتیاکسیدانی</h3>
|
||
<p>مصرف میوهها و سبزیجات رنگارنگ غنی از ویتامین C و E مفید است.</p>
|
||
<h2>نتیجهگیری</h2>
|
||
<p>مراقبت از پوست در تابستان نیاز به برنامهریزی درست و استمرار دارد. برای مشاوره بیشتر با دکتر سوسن آلطه تماس بگیرید.</p>"
|
||
},
|
||
|
||
new BlogPost {
|
||
Title="فیلر لب و صورت: همه چیز درباره تزریق فیلر اسید هیالورونیک", Slug="filler-lab-soorat",
|
||
Excerpt="فیلر لب و صورت با اسید هیالورونیک یکی از محبوبترین روشهای زیبایی است. همه چیز درباره فیلر، انواع، مزایا و نکات مهم.",
|
||
FocusKeyword="فیلر لب", Keywords="فیلر لب,فیلر صورت,تزریق فیلر,اسید هیالورونیک,فیلر چیست,حجم دهی لب",
|
||
MetaTitle="فیلر لب و صورت | راهنمای کامل تزریق فیلر | دکتر آلطه",
|
||
MetaDescription="همه چیز درباره فیلر لب و صورت: انواع فیلر، مزایا، ماندگاری و نکات قبل و بعد از تزریق. راهنمای دکتر سوسن آلطه.",
|
||
ArticleType="MedicalWebPage", IsPublished=true, PublishedAt=DateTime.UtcNow.AddDays(-2),
|
||
CategoryId=cat1.Id, ReadingTimeMinutes=6,
|
||
Content=@"<h2>فیلر چیست؟</h2>
|
||
<p>فیلرهای پوستی موادی هستند که به صورت تزریقی برای پر کردن خطوط، افزایش حجم و بهبود ظاهر پوست استفاده میشوند. محبوبترین نوع، اسید هیالورونیک است - مادهای که به طور طبیعی در پوست وجود دارد.</p>
|
||
<h2>کاربردهای فیلر</h2>
|
||
<ul><li><strong>فیلر لب:</strong> حجم دهی، تعریف و تقارن لبها</li><li><strong>فیلر گونه:</strong> برجستهتر کردن استخوان گونه</li><li><strong>فیلر زیر چشم:</strong> رفع تیرگی و گودی زیر چشم</li><li><strong>فیلر چانه و فک:</strong> تعریف خط فک</li><li><strong>فیلر بینی (رینوفیلر):</strong> اصلاح شکل بینی بدون جراحی</li></ul>
|
||
<h2>تفاوت فیلر و بوتاکس</h2>
|
||
<ul><li><strong>بوتاکس:</strong> عضلات را شل میکند و چینهای ناشی از حرکت را درمان میکند</li><li><strong>فیلر:</strong> حجم اضافه میکند و گودیها را پر میکند</li></ul>
|
||
<h2>ماندگاری فیلرها</h2>
|
||
<ul><li>لب: ۶ تا ۱۲ ماه</li><li>گونه: ۱۲ تا ۱۸ ماه</li><li>زیر چشم: ۶ تا ۱۸ ماه</li></ul>
|
||
<h2>نکات قبل از تزریق فیلر</h2>
|
||
<ul><li>یک هفته قبل از مصرف آسپرین خودداری کنید</li><li>در صورت تبخال فعال تزریق را به تعویق بیندازید</li><li>با پزشک درباره انتظارات واقعبینانه صحبت کنید</li></ul>
|
||
<h2>نتیجهگیری</h2>
|
||
<p>فیلر اسید هیالورونیک یکی از بیخطرترین و موثرترین روشهای زیبایی است. برای رزرو مشاوره با دکتر سوسن آلطه با ما تماس بگیرید.</p>"
|
||
},
|
||
|
||
new BlogPost {
|
||
Title="لیزر بیکینی: راهنمای کامل، مزایا و نکات ایمنی", Slug="laser-bikini",
|
||
Excerpt="لیزر بیکینی پرخواهترین خدمت لیزر موهای زائد در ایران است. از مزایا و انواع تا تعداد جلسات و مراقبتهای لازم — راهنمای جامع دکتر سوسن آلطه.",
|
||
FocusKeyword="لیزر بیکینی", Keywords="لیزر بیکینی,لیزر ناحیه بیکینی,حذف موهای زائد بیکینی,لیزر بیکینی برزیلی,لیزر دیود بیکینی",
|
||
MetaTitle="لیزر بیکینی | راهنمای کامل ۱۴۰۳ | دکتر سوسن آلطه",
|
||
MetaDescription="راهنمای کامل لیزر بیکینی: انواع، مزایا، تعداد جلسات و مراقبتهای بعد. لیزر بیکینی با دستگاههای پیشرفته نزد دکتر سوسن آلطه تهران.",
|
||
ArticleType="MedicalWebPage", IsPublished=true, PublishedAt=DateTime.UtcNow.AddDays(-9),
|
||
CategoryId=cat3.Id, ReadingTimeMinutes=7,
|
||
Content=@"<h2>لیزر بیکینی چیست؟</h2>
|
||
<p>لیزر بیکینی یکی از پیشرفتهترین روشهای حذف دائمی موهای زائد ناحیه بیکینی است. این روش با انرژی لیزر، فولیکولهای مو را هدف قرار میدهد و رشد مجدد آنها را متوقف میکند. برخلاف اپیلاسیون و مومکاری که موقتی هستند، لیزر نتایج طولانیمدت و حتی دائمی ارائه میدهد.</p>
|
||
<h2>انواع لیزر بیکینی</h2>
|
||
<ul>
|
||
<li><strong>لیزر بیکینی لاین:</strong> حذف موهای بیرونزده از لبه شورت — مناسب برای شروع.</li>
|
||
<li><strong>لیزر بیکینی کامل (برزیلی):</strong> حذف کامل موهای ناحیه تناسلی — پرطرفدارترین گزینه.</li>
|
||
<li><strong>لیزر بیکینی هایبرید:</strong> ترکیب دو روش بالا با تنظیم شخصی برای هر بیمار.</li>
|
||
</ul>
|
||
<h2>بهترین لیزر برای ناحیه بیکینی</h2>
|
||
<p>دستگاه لیزر دیود (۸۰۸ نانومتر) به دلیل عمق نفوذ مناسب و سیستم خنککننده یکپارچه، مناسبترین گزینه برای ناحیه بیکینی است. این دستگاه برای طیف گستردهای از رنگ پوست ایرانی ایمن و موثر است.</p>
|
||
<h2>مزایای لیزر بیکینی</h2>
|
||
<ul>
|
||
<li>کاهش ۸۵ تا ۹۵ درصدی رشد مو پس از تکمیل دوره درمانی</li>
|
||
<li>جلوگیری از جوشهای ناشی از اصلاح و موهای فرورفته</li>
|
||
<li>بهبود رنگ و بافت پوست ناحیه</li>
|
||
<li>بدون درد با دستگاههای مجهز به سیستم خنککنندگی</li>
|
||
<li>هزینه کمتر در بلندمدت نسبت به روشهای موقت</li>
|
||
</ul>
|
||
<h2>تعداد جلسات لیزر بیکینی</h2>
|
||
<p>برای حذف کامل موهای بیکینی معمولاً به ۶ تا ۸ جلسه نیاز است. فاصله بین جلسات ۶ هفته است تا چرخه رشد تمام فولیکولها پوشش داده شود. پوستهای روشنتر با موهای تیره سریعتر پاسخ میدهند.</p>
|
||
<h2>مراقبتهای قبل از لیزر بیکینی</h2>
|
||
<ol>
|
||
<li>۴ هفته قبل از مومکاری یا اپیلاسیون خودداری کنید</li>
|
||
<li>۲۴ ساعت قبل ناحیه را با تیغ اصلاح کنید (نه نزدیکتر)</li>
|
||
<li>از کرمهای خودافشرگر یا برنزهکننده در ۲ هفته قبل بپرهیزید</li>
|
||
<li>در روز جلسه دئودورانت یا کرم نزنید</li>
|
||
</ol>
|
||
<h2>مراقبتهای بعد از لیزر بیکینی</h2>
|
||
<ul>
|
||
<li>۴۸ ساعت از سونا، استخر و حمام آب داغ بپرهیزید</li>
|
||
<li>از پوشاک نخی و گشاد استفاده کنید</li>
|
||
<li>کرم آبرسان ملایم روی ناحیه بزنید</li>
|
||
<li>از خاراندن ناحیه خودداری کنید</li>
|
||
</ul>
|
||
<h2>نتیجهگیری</h2>
|
||
<p>لیزر بیکینی با فناوری روز دنیا در مطب دکتر سوسن آلطه با رعایت کامل بهداشت و ایمنی انجام میشود. همین امروز برای مشاوره رایگان تماس بگیرید.</p>"
|
||
},
|
||
|
||
new BlogPost {
|
||
Title="فیلر بدن: روش غیرجراحی اصلاح فرم و رفع فرورفتگیهای بدن", Slug="filler-badan",
|
||
Excerpt="فیلر بدن راهکاری نوین برای اصلاح فرم، رفع اسکار، فرورفتگیها و بهبود ظاهر بدن بدون جراحی است. مزایا، کاربردها و نکات تخصصی.",
|
||
FocusKeyword="فیلر بدن", Keywords="فیلر بدن,تزریق فیلر بدن,اصلاح فرم بدن,فیلر اسکار بدن,فیلر فرورفتگی,درمان سلولیت",
|
||
MetaTitle="فیلر بدن | اصلاح فرم غیرجراحی | دکتر سوسن آلطه",
|
||
MetaDescription="فیلر بدن: روشی نوین و غیرجراحی برای رفع فرورفتگیها، اسکار و اصلاح فرم بدن. راهنمای تخصصی دکتر سوسن آلطه.",
|
||
ArticleType="MedicalWebPage", IsPublished=true, PublishedAt=DateTime.UtcNow.AddDays(-7),
|
||
CategoryId=cat1.Id, ReadingTimeMinutes=6,
|
||
Content=@"<h2>فیلر بدن چیست؟</h2>
|
||
<p>فیلر بدن به تزریق مواد پرکننده — عمدتاً اسید هیالورونیک یا هیدروکسیآپاتیت کلسیم — به نواحی مختلف بدن (غیر از صورت) گفته میشود. هدف اصلی این روش پر کردن فرورفتگیها، صاف کردن اسکارها، بهبود بافت پوست و فرمدهی به نواحی موردنظر بدون نیاز به جراحی است.</p>
|
||
<h2>کاربردهای فیلر بدن</h2>
|
||
<ul>
|
||
<li><strong>رفع اسکار و فرورفتگیهای بدن:</strong> جای زخم، جراحی یا اسکار آکنه روی پشت</li>
|
||
<li><strong>درمان سلولیت:</strong> صاف کردن پوست پرتقالی ناحیه ران و باسن</li>
|
||
<li><strong>فرمدهی به باسن:</strong> افزایش حجم و برجستگی بدون ایمپلنت</li>
|
||
<li><strong>رفع خطوط دکلته:</strong> جوانسازی ناحیه گردن و سینه</li>
|
||
<li><strong>ترمیم دستها:</strong> رفع چروک و فرورفتگی پشت دست</li>
|
||
</ul>
|
||
<h2>انواع فیلرهای مورد استفاده در بدن</h2>
|
||
<h3>اسید هیالورونیک (HA)</h3>
|
||
<p>محبوبترین و بیخطرترین نوع. ماندگاری ۶ تا ۱۸ ماه. قابل بازگشت با تزریق هیالورونیداز.</p>
|
||
<h3>هیدروکسیآپاتیت کلسیم (Radiesse)</h3>
|
||
<p>برای فرمدهی عمیقتر و نتایج ماندگارتر (۱۸ تا ۲۴ ماه). همچنین محرک ساخت کلاژن طبیعی است.</p>
|
||
<h2>مزایای فیلر بدن</h2>
|
||
<ul>
|
||
<li>غیرجراحی — بدون بیهوشی و بستری</li>
|
||
<li>زمان بهبودی کوتاه (معمولاً ۲۴ تا ۴۸ ساعت)</li>
|
||
<li>نتایج فوری و قابل مشاهده</li>
|
||
<li>قابل تنظیم و شخصیسازیشده</li>
|
||
<li>خطر عفونت بسیار کمتر از جراحی</li>
|
||
</ul>
|
||
<h2>نکات قبل از تزریق فیلر بدن</h2>
|
||
<ol>
|
||
<li>یک هفته قبل از NSAID و آسپرین خودداری کنید</li>
|
||
<li>سابقه آلرژی به مواد فیلر را به پزشک اطلاع دهید</li>
|
||
<li>بارداری و شیردهی از موارد منع مصرف است</li>
|
||
<li>انتظارات واقعبینانه با پزشک در میان بگذارید</li>
|
||
</ol>
|
||
<h2>نتیجهگیری</h2>
|
||
<p>فیلر بدن راهحلی ایمن و موثر برای اصلاح ظاهر بدن است. دکتر سوسن آلطه با تجربه و تخصص در این حوزه آماده مشاوره و درمان شما است.</p>"
|
||
},
|
||
|
||
new BlogPost {
|
||
Title="هایفو (HIFU): لیفتینگ غیرجراحی صورت با اولتراسوند متمرکز", Slug="hifu-lifting",
|
||
Excerpt="هایفو پیشرفتهترین روش لیفتینگ غیرجراحی صورت است. همه چیز درباره HIFU، نحوه عملکرد، مزایا، ماندگاری و چه کسانی کاندید مناسبی هستند.",
|
||
FocusKeyword="هایفو", Keywords="هایفو,HIFU,لیفتینگ غیرجراحی,اولتراسوند متمرکز,جوانسازی پوست,لیفت صورت بدون جراحی,هایفو تهران",
|
||
MetaTitle="هایفو HIFU | لیفتینگ غیرجراحی صورت | دکتر سوسن آلطه",
|
||
MetaDescription="هایفو: لیفتینگ غیرجراحی صورت با اولتراسوند متمرکز. سفتشدن پوست، لیفت ابرو و گونه بدون جراحی. دکتر سوسن آلطه تهران.",
|
||
ArticleType="MedicalWebPage", IsPublished=true, PublishedAt=DateTime.UtcNow.AddDays(-6),
|
||
CategoryId=cat3.Id, ReadingTimeMinutes=7,
|
||
Content=@"<h2>هایفو (HIFU) چیست؟</h2>
|
||
<p>هایفو (High-Intensity Focused Ultrasound) یا اولتراسوند متمرکز با شدت بالا، یکی از پیشرفتهترین فناوریهای لیفتینگ غیرجراحی پوست است. این دستگاه با ارسال امواج اولتراسوند به لایههای عمیق پوست (SMAS — همان لایهای که در جراحی لیفت سنتی هدف قرار میگیرد)، گرمای دقیق ایجاد میکند که منجر به سفت شدن بافت و تحریک ساخت کلاژن جدید میشود.</p>
|
||
<h2>نحوه عملکرد هایفو</h2>
|
||
<p>دستگاه هایفو انرژی اولتراسوند را به نقاط کانونی دقیق در عمق ۱.۵، ۳ و ۴.۵ میلیمتری پوست متمرکز میکند. این گرمای کنترلشده بین ۶۰ تا ۷۰ درجه سانتیگراد، بافت کلاژن را منقبض کرده و سلولهای فیبروبلاست را تحریک به تولید کلاژن جدید میکند. نتیجه: پوستی سفتتر، بالاتر و جوانتر.</p>
|
||
<h2>کاربردهای هایفو</h2>
|
||
<ul>
|
||
<li><strong>لیفت ابرو:</strong> باز شدن چشمها و بالا آمدن ابروها</li>
|
||
<li><strong>سفت کردن گونهها:</strong> بازگشت حجم و فرم جوانی به گونه</li>
|
||
<li><strong>رفع شلی زیر چانه:</strong> از بین بردن دابل چین</li>
|
||
<li><strong>لیفت گردن:</strong> سفت کردن پوست شل گردن</li>
|
||
<li><strong>سفت کردن پوست بدن:</strong> شکم، بازو و ران</li>
|
||
</ul>
|
||
<h2>چه کسانی کاندید هایفو هستند؟</h2>
|
||
<p>هایفو برای افراد ۳۰ تا ۶۵ ساله که افتادگی خفیف تا متوسط پوست دارند، ایدهآل است. افرادی که شلی شدید دارند یا به نتایج جراحی لیفت نیاز دارند، ممکن است از هایفو به تنهایی راضی نشوند.</p>
|
||
<h2>مزایای هایفو</h2>
|
||
<ul>
|
||
<li>بدون جراحی، بدون بیهوشی، بدون بخیه</li>
|
||
<li>یک جلسه کافی است — بدون نیاز به دوره درمانی طولانی</li>
|
||
<li>زمان بهبودی صفر — میتوانید بلافاصله به کار برگردید</li>
|
||
<li>نتایج طبیعی که به تدریج ظاهر میشوند</li>
|
||
<li>ماندگاری ۱ تا ۲ سال با یک جلسه</li>
|
||
</ul>
|
||
<h2>ماندگاری نتایج هایفو</h2>
|
||
<p>نتایج هایفو به تدریج در طی ۲ تا ۶ ماه بعد از جلسه ظاهر میشوند چون ساخت کلاژن جدید زمان میبرد. اثرات معمولاً ۱۲ تا ۱۸ ماه پایدار هستند و با یک جلسه تکمیلی میتوان آن را حفظ کرد.</p>
|
||
<h2>نتیجهگیری</h2>
|
||
<p>هایفو گزینهای عالی برای کسانی است که میخواهند بدون جراحی به نتایج لیفت واقعی برسند. برای ارزیابی و تعیین اینکه آیا هایفو برای شما مناسب است، با دکتر سوسن آلطه مشاوره بگیرید.</p>"
|
||
},
|
||
|
||
new BlogPost {
|
||
Title="RF لیفتینگ: جوانسازی پوست با رادیوفرکوئنسی بدون جراحی", Slug="rf-lifting",
|
||
Excerpt="RF لیفتینگ با رادیوفرکوئنسی یکی از بهترین روشهای غیرتهاجمی سفت کردن و جوانسازی پوست است. مکانیزم، مزایا، تفاوت با هایفو و انتظارات واقعی.",
|
||
FocusKeyword="RF لیفتینگ", Keywords="RF لیفتینگ,رادیوفرکوئنسی پوست,سفت کردن پوست,جوانسازی RF,رادیوفرکوئنسی صورت",
|
||
MetaTitle="RF لیفتینگ | رادیوفرکوئنسی پوست | دکتر سوسن آلطه",
|
||
MetaDescription="RF لیفتینگ با رادیوفرکوئنسی: سفتکردن پوست، رفع چین و چروک و جوانسازی بدون جراحی. دکتر سوسن آلطه متخصص زیبایی تهران.",
|
||
ArticleType="MedicalWebPage", IsPublished=true, PublishedAt=DateTime.UtcNow.AddDays(-4),
|
||
CategoryId=cat3.Id, ReadingTimeMinutes=6,
|
||
Content=@"<h2>RF لیفتینگ چیست؟</h2>
|
||
<p>RF لیفتینگ یا رادیوفرکوئنسیتراپی، یک روش غیرتهاجمی جوانسازی پوست است که از امواج رادیویی کنترلشده برای گرم کردن لایههای میانی پوست (درم) استفاده میکند. این گرمای کنترلشده باعث انقباض فوری رشتههای کلاژن و تحریک ساخت کلاژن و الاستین جدید میشود.</p>
|
||
<h2>تفاوت RF و هایفو</h2>
|
||
<table style=""width:100%;border-collapse:collapse;margin:1rem 0"">
|
||
<tr style=""background:#F5ECD8""><th style=""padding:.6rem;text-align:right;border:1px solid #ddd"">ویژگی</th><th style=""padding:.6rem;text-align:right;border:1px solid #ddd"">RF</th><th style=""padding:.6rem;text-align:right;border:1px solid #ddd"">هایفو</th></tr>
|
||
<tr><td style=""padding:.6rem;border:1px solid #ddd"">عمق نفوذ</td><td style=""padding:.6rem;border:1px solid #ddd"">درم (۲-۳ میلیمتر)</td><td style=""padding:.6rem;border:1px solid #ddd"">SMAS (تا ۴.۵ میلیمتر)</td></tr>
|
||
<tr><td style=""padding:.6rem;border:1px solid #ddd"">تعداد جلسات</td><td style=""padding:.6rem;border:1px solid #ddd"">۴ تا ۶ جلسه</td><td style=""padding:.6rem;border:1px solid #ddd"">۱ تا ۲ جلسه</td></tr>
|
||
<tr><td style=""padding:.6rem;border:1px solid #ddd"">مناسب برای</td><td style=""padding:.6rem;border:1px solid #ddd"">چینهای سطحی، سفتکاری</td><td style=""padding:.6rem;border:1px solid #ddd"">افتادگی متوسط تا شدید</td></tr>
|
||
</table>
|
||
<h2>کاربردهای RF لیفتینگ</h2>
|
||
<ul>
|
||
<li>سفت کردن پوست شل صورت و گردن</li>
|
||
<li>کاهش ظاهر چین و چروکهای ریز</li>
|
||
<li>بهبود بافت کلی پوست</li>
|
||
<li>کوچکشدن منافذ باز پوست</li>
|
||
<li>RF بدن: سفت کردن پوست شکم، بازو و ران</li>
|
||
</ul>
|
||
<h2>مزایای RF لیفتینگ</h2>
|
||
<ul>
|
||
<li>بدون جراحی و بدون دوره نقاهت</li>
|
||
<li>مناسب برای تمام رنگهای پوست</li>
|
||
<li>قابل ترکیب با سایر روشهای زیبایی</li>
|
||
<li>راحتتر و مقرونبهصرفهتر از هایفو</li>
|
||
</ul>
|
||
<h2>دوره درمانی پیشنهادی</h2>
|
||
<p>برای بهترین نتیجه، ۴ تا ۶ جلسه با فاصله ۲ هفته توصیه میشود. نتایج تدریجی ظاهر میشوند و با یک جلسه نگهدارنده هر ۶ ماه حفظ میشوند.</p>
|
||
<h2>نتیجهگیری</h2>
|
||
<p>RF لیفتینگ گزینهای ایدهآل برای کسانی است که میخواهند پوستشان را سفتتر و جوانتر کنند. برای تشخیص مناسبترین روش، با دکتر سوسن آلطه مشاوره کنید.</p>"
|
||
},
|
||
|
||
new BlogPost {
|
||
Title="درمان لک صورت: بهترین روشهای علمی برای پوست روشن و یکنواخت", Slug="darman-lak-surat",
|
||
Excerpt="لک صورت یکی از شایعترین مشکلات پوستی در ایران است. انواع لک، علتها و بهترین روشهای درمانی از کرم تا لیزر.",
|
||
FocusKeyword="درمان لک صورت", Keywords="درمان لک صورت,لک پوستی,لک صورت,درمان هایپرپیگمنتاسیون,ملاسما,لک آفتاب,روشنکردن پوست",
|
||
MetaTitle="درمان لک صورت | بهترین روشها | دکتر سوسن آلطه",
|
||
MetaDescription="درمان تخصصی لک صورت: از ملاسما تا لک آفتاب. بهترین کرمها، پیلینگ شیمیایی و لیزر لک. راهنمای دکتر سوسن آلطه.",
|
||
ArticleType="MedicalWebPage", IsPublished=true, PublishedAt=DateTime.UtcNow.AddDays(-3),
|
||
CategoryId=cat3.Id, ReadingTimeMinutes=8,
|
||
Content=@"<h2>لک صورت چیست و چرا ایجاد میشود؟</h2>
|
||
<p>لک صورت یا هایپرپیگمنتاسیون به تجمع بیش از حد رنگدانه ملانین در نواحی خاصی از پوست گفته میشود که منجر به تیرهتر شدن آن ناحیه میشود. این مشکل یکی از شایعترین علل مراجعه به متخصصان پوست در ایران است.</p>
|
||
<h2>انواع لک صورت</h2>
|
||
<ul>
|
||
<li><strong>ملاسما:</strong> لکهای قهوهای متقارن که اغلب روی گونه، پیشانی و بالای لب ظاهر میشوند. شایعترین نوع در خانمهای ایرانی، اغلب ناشی از تغییرات هورمونی یا قرصهای ضدبارداری.</li>
|
||
<li><strong>لک آفتاب (Solar lentigines):</strong> لکهای ناشی از قرارگیری طولانیمدت زیر نور خورشید.</li>
|
||
<li><strong>لک پس از التهاب (PIH):</strong> تیرگی باقیمانده بعد از جوش یا آسیب پوستی.</li>
|
||
<li><strong>لک بارداری (Chloasma):</strong> نوع خاصی از ملاسما در دوران بارداری.</li>
|
||
</ul>
|
||
<h2>بهترین روشهای درمان لک صورت</h2>
|
||
<h3>۱. کرمهای موضعی</h3>
|
||
<ul>
|
||
<li><strong>هیدروکینون ۲-۴٪:</strong> موثرترین ماده روشنکننده پوست</li>
|
||
<li><strong>ترتینوئین (رتینوئیک اسید):</strong> تسریع تجدید سلولی</li>
|
||
<li><strong>اسید آزلائیک:</strong> مناسب برای پوستهای حساس</li>
|
||
<li><strong>ویتامین C (اسید اسکوربیک):</strong> آنتیاکسیدان روشنکننده</li>
|
||
<li><strong>نیاسینامید:</strong> کاهش انتقال ملانین به سطح پوست</li>
|
||
</ul>
|
||
<h3>۲. پیلینگ شیمیایی</h3>
|
||
<p>اسیدهای لایهبردار مانند TCA، گلیکولیک اسید و لاکتیک اسید لایههای بالایی پوست را از بین میبرند و پوست تازهتر را آشکار میکنند.</p>
|
||
<h3>۳. لیزر درمانی</h3>
|
||
<p>لیزر Q-Switched Nd:YAG و لیزر فرکشنال برای لکهای مقاوم بسیار موثر هستند. این روشها انرژی لیزر را دقیقاً به ملانوزومها میرسانند.</p>
|
||
<h3>۴. مزوتراپی روشنکننده</h3>
|
||
<p>تزریق ترانگزامیک اسید، ویتامین C و گلوتاتیون مستقیم به پوست برای نتایج سریعتر.</p>
|
||
<h2>پیشگیری از لک صورت</h2>
|
||
<ul>
|
||
<li>استفاده روزانه از ضدآفتاب SPF ۵۰+ — مهمترین اقدام</li>
|
||
<li>پرهیز از آفتاب در اوج ساعات تابش</li>
|
||
<li>عدم دستکاری جوشهای صورت</li>
|
||
<li>استفاده از کلاه و عینک آفتابی</li>
|
||
</ul>
|
||
<h2>نتیجهگیری</h2>
|
||
<p>درمان لک صورت نیاز به تشخیص دقیق نوع لک و پروتکل درمانی مناسب دارد. با دکتر سوسن آلطه مشاوره بگیرید و بهترین برنامه درمانی را دریافت کنید.</p>"
|
||
},
|
||
|
||
new BlogPost {
|
||
Title="روتین مراقبت از پوست: راهنمای کامل برنامه روزانه برای هر نوع پوست", Slug="rotin-moraghebet-poost",
|
||
Excerpt="روتین مراقبت از پوست پایه سلامت و زیبایی پوست است. راهنمای کامل از پاکسازی تا ضدآفتاب برای پوستهای مختلف.",
|
||
FocusKeyword="روتین مراقبت از پوست", Keywords="روتین مراقبت از پوست,برنامه مراقبت از پوست,اسکینکر روتین,مرطوبکننده,ضدآفتاب,پاکسازی صورت",
|
||
MetaTitle="روتین مراقبت از پوست | راهنمای کامل | دکتر سوسن آلطه",
|
||
MetaDescription="روتین کامل مراقبت از پوست برای صبح و شب. بهترین محصولات و ترتیب استفاده برای هر نوع پوست. راهنمای تخصصی دکتر آلطه.",
|
||
ArticleType="Article", IsPublished=true, PublishedAt=DateTime.UtcNow.AddDays(-1),
|
||
CategoryId=cat2.Id, ReadingTimeMinutes=9,
|
||
Content=@"<h2>چرا روتین مراقبت از پوست مهم است؟</h2>
|
||
<p>پوست سالم نیاز به مراقبت منظم دارد. روتین صحیح پوست میتواند از پیری زودرس جلوگیری کند، مشکلات پوستی را کاهش دهد و درخشندگی طبیعی پوست را حفظ کند. مهمتر از همه، یک روتین خوب از بروز مشکلات جدی پوستی پیشگیری میکند.</p>
|
||
<h2>شناخت نوع پوست</h2>
|
||
<p>قبل از هر چیز باید نوع پوست خود را بشناسید:</p>
|
||
<ul>
|
||
<li><strong>پوست چرب:</strong> براقی، منافذ بزرگ، مستعد آکنه</li>
|
||
<li><strong>پوست خشک:</strong> احساس کشیدگی، پوستهپوسته شدن، ناراحتی</li>
|
||
<li><strong>پوست مختلط:</strong> ناحیه T چرب، گونهها خشک یا نرمال</li>
|
||
<li><strong>پوست حساس:</strong> واکنش به محصولات، قرمزی، خارش</li>
|
||
<li><strong>پوست نرمال:</strong> بالانس، بدون مشکل خاص</li>
|
||
</ul>
|
||
<h2>روتین صبح</h2>
|
||
<h3>مرحله ۱: پاکسازی ملایم</h3>
|
||
<p>با یک شوینده ملایم متناسب با نوع پوستتان شروع کنید. پوست چرب: ژل پاککننده. پوست خشک: شوینده کرمی.</p>
|
||
<h3>مرحله ۲: تونر (اختیاری)</h3>
|
||
<p>تونر pH پوست را متعادل میکند و آمادهسازی بهتر برای مراحل بعدی.</p>
|
||
<h3>مرحله ۳: سرم</h3>
|
||
<p>سرم ویتامین C برای صبح عالی است — آنتیاکسیدان، روشنکننده و محافظ در برابر آلودگی.</p>
|
||
<h3>مرحله ۴: مرطوبکننده</h3>
|
||
<p>همه انواع پوست به مرطوبکننده نیاز دارند. پوست چرب: ژل سبک. پوست خشک: کرم غنی.</p>
|
||
<h3>مرحله ۵: ضدآفتاب — مهمترین مرحله</h3>
|
||
<p>SPF ۵۰+ هر روز بدون استثنا. این مرحله را هرگز حذف نکنید حتی در روزهای ابری.</p>
|
||
<h2>روتین شب</h2>
|
||
<h3>مرحله ۱: پاکسازی دوگانه (Double Cleanse)</h3>
|
||
<p>ابتدا با بالم یا روغن پاککننده آرایش را پاک کنید. سپس با شوینده آبی پوست را عمیقتر تمیز کنید.</p>
|
||
<h3>مرحله ۲: اکسفولیانت (۲ بار در هفته)</h3>
|
||
<p>اسیدهای AHA (گلیکولیک، لاکتیک) یا BHA (سالیسیلیک اسید) را شبانه استفاده کنید.</p>
|
||
<h3>مرحله ۳: سرم شبانه</h3>
|
||
<p>رتینول یا رتینوئیک اسید برای شب. از کم شروع کنید و تدریجاً غلظت را افزایش دهید.</p>
|
||
<h3>مرحله ۴: مرطوبکننده شبانه</h3>
|
||
<p>کرمهای شبانه غنیتر از مرطوبکننده روز هستند و برای بازسازی پوست در شب طراحی شدهاند.</p>
|
||
<h2>اشتباهات رایج در روتین پوست</h2>
|
||
<ul>
|
||
<li>استفاده از تعداد زیادی محصول به طور همزمان</li>
|
||
<li>حذف ضدآفتاب در روزهای ابری</li>
|
||
<li>شستن بیش از حد صورت</li>
|
||
<li>استفاده از رتینول و اسید در یک روتین بدون راهنمایی پزشک</li>
|
||
<li>تعویض محصولات قبل از دادن زمان کافی برای تاثیر</li>
|
||
</ul>
|
||
<h2>نتیجهگیری</h2>
|
||
<p>یک روتین ساده اما منظم بهتر از چندین محصول گرانقیمت بدون نظم است. برای دریافت روتین شخصیسازیشده با دکتر سوسن آلطه مشاوره بگیرید.</p>"
|
||
},
|
||
|
||
new BlogPost {
|
||
Title="پاکسازی پوست صورت: روشهای حرفهای و خانگی برای پوست درخشان", Slug="pakasazi-poost-surat",
|
||
Excerpt="پاکسازی پوست صورت پایه هر روتین مراقبتی است. تفاوت پاکسازی خانگی و حرفهای مطب، بهترین شویندهها و روشهای تخصصی.",
|
||
FocusKeyword="پاکسازی پوست صورت", Keywords="پاکسازی پوست صورت,پاکسازی حرفهای پوست,شوینده صورت,پاکسازی مطب,اکسترکشن جوش",
|
||
MetaTitle="پاکسازی پوست صورت | حرفهای و خانگی | دکتر سوسن آلطه",
|
||
MetaDescription="راهنمای کامل پاکسازی پوست صورت: تفاوت پاکسازی خانگی و مطب، بهترین شویندهها و تعداد جلسات توصیهشده.",
|
||
ArticleType="Article", IsPublished=true, PublishedAt=DateTime.UtcNow.AddDays(-12),
|
||
CategoryId=cat2.Id, ReadingTimeMinutes=7,
|
||
Content=@"<h2>چرا پاکسازی پوست ضروری است؟</h2>
|
||
<p>در طول روز، پوست با ذرات گرد و غبار، آلایندههای محیطی، چربی طبیعی، سلولهای مرده و باقیمانده محصولات آرایشی پوشیده میشود. اگر این مواد به موقع پاکسازی نشوند، منافذ مسدود شده و زمینه برای آکنه، جوشهای سرسیاه و تیرگی فراهم میشود.</p>
|
||
<h2>پاکسازی روزانه خانگی</h2>
|
||
<h3>انتخاب شوینده مناسب</h3>
|
||
<ul>
|
||
<li><strong>پوست چرب:</strong> ژل پاککننده حاوی سالیسیلیک اسید یا BHA</li>
|
||
<li><strong>پوست خشک:</strong> شوینده کرمی یا شیرپاککننده بدون صابون</li>
|
||
<li><strong>پوست حساس:</strong> شوینده بدون عطر، آلوئهورا، بسیار ملایم</li>
|
||
<li><strong>پوست مختلط:</strong> فوم ملایم یا میسلار واتر</li>
|
||
</ul>
|
||
<h3>روش صحیح شستن صورت</h3>
|
||
<ol>
|
||
<li>دستها را بشویید</li>
|
||
<li>پوست را با آب ولرم (نه داغ) خیس کنید</li>
|
||
<li>شوینده را به آرامی با حرکات دورانی ماساژ دهید (۶۰ ثانیه)</li>
|
||
<li>کاملاً آبکشی کنید</li>
|
||
<li>با حوله تمیز به آرامی خشک کنید</li>
|
||
</ol>
|
||
<h2>پاکسازی حرفهای در مطب</h2>
|
||
<p>پاکسازی حرفهای (Deep Cleansing Facial) فراتر از پاکسازی خانگی است و شامل مراحل تخصصی میشود:</p>
|
||
<ol>
|
||
<li><strong>پاکسازی اولیه:</strong> حذف آرایش و چربی سطحی</li>
|
||
<li><strong>بخاردهی:</strong> باز شدن منافذ با بخار گرم</li>
|
||
<li><strong>لایهبرداری:</strong> حذف سلولهای مرده با اسکراب یا آنزیمی</li>
|
||
<li><strong>اکسترکشن:</strong> خارج کردن تخصصی جوشهای سرسیاه و سرسفید</li>
|
||
<li><strong>ماسک:</strong> ماسک متناسب با نوع پوست (آبرسان، ضدآکنه، روشنکننده)</li>
|
||
<li><strong>مرطوبکنندگی و محافظت:</strong> کرم مناسب و SPF</li>
|
||
</ol>
|
||
<h2>تعداد توصیهشده پاکسازی مطب</h2>
|
||
<p>برای اکثر افراد یک جلسه پاکسازی در مطب هر ۴ تا ۶ هفته مناسب است. پوستهای بسیار چرب یا مستعد آکنه ممکن است هر ۳ هفته نیاز داشته باشند.</p>
|
||
<h2>اشتباهات رایج در پاکسازی پوست</h2>
|
||
<ul>
|
||
<li>فشار دادن جوشها با ناخن — ایجاد اسکار و عفونت</li>
|
||
<li>استفاده از آب داغ — از بین رفتن چربی طبیعی پوست</li>
|
||
<li>شستشوی بیش از دو بار در روز — خشکی و تحریک پوست</li>
|
||
<li>فراموش کردن پاکسازی شبانه — خطرناکترین اشتباه</li>
|
||
</ul>
|
||
<h2>نتیجهگیری</h2>
|
||
<p>پاکسازی صحیح پوست اساس هر برنامه مراقبتی موفق است. برای دریافت پاکسازی تخصصی و توصیههای شخصیسازیشده، با مطب دکتر سوسن آلطه تماس بگیرید.</p>"
|
||
},
|
||
|
||
new BlogPost {
|
||
Title="تزریق ژل لب: راهنمای کامل حجمدهی و تعریف لب با فیلر", Slug="tazriq-jel-lab",
|
||
Excerpt="تزریق ژل لب با اسید هیالورونیک پرطرفدارترین روش زیبایی است. از انتخاب نوع فیلر تا نتایج طبیعی — راهنمای تخصصی دکتر سوسن.",
|
||
FocusKeyword="تزریق ژل لب", Keywords="تزریق ژل لب,فیلر لب,حجم دهی لب,تزریق فیلر لب,لب طبیعی با فیلر,ژل لب تهران",
|
||
MetaTitle="تزریق ژل لب | حجمدهی طبیعی | دکتر سوسن آلطه تهران",
|
||
MetaDescription="تزریق ژل لب با اسید هیالورونیک برای حجمدهی طبیعی. راهنمای کامل از انتخاب فیلر تا مراقبتهای بعد. دکتر سوسن آلطه تهران.",
|
||
ArticleType="MedicalWebPage", IsPublished=true, PublishedAt=DateTime.UtcNow.AddDays(-11),
|
||
CategoryId=cat1.Id, ReadingTimeMinutes=6,
|
||
Content=@"<h2>تزریق ژل لب چیست؟</h2>
|
||
<p>تزریق ژل لب یا فیلر لب، استفاده از مواد پرکننده — عمدتاً اسید هیالورونیک — برای افزایش حجم، تعریف خطوط و بهبود تقارن لبهاست. این روش یکی از پرتقاضاترین پروسیجرهای زیبایی در ایران است.</p>
|
||
<h2>چرا اسید هیالورونیک بهترین انتخاب است؟</h2>
|
||
<p>اسید هیالورونیک مادهای طبیعی است که در بدن انسان وجود دارد. فیلرهای HA به دلیل زیر برتری دارند:</p>
|
||
<ul>
|
||
<li>نتایج کاملاً طبیعی</li>
|
||
<li>قابل برگشت با تزریق هیالورونیداز</li>
|
||
<li>ماندگاری ۶ تا ۱۲ ماه</li>
|
||
<li>کمترین خطر آلرژی</li>
|
||
</ul>
|
||
<h2>انواع تزریق ژل لب</h2>
|
||
<ul>
|
||
<li><strong>حجمدهی لب (Volume):</strong> افزایش کلی حجم لب برای ظاهر پرتر</li>
|
||
<li><strong>تعریف خطوط (Definition):</strong> برجسته کردن Cupid's bow و تعریف لبه لب</li>
|
||
<li><strong>اصلاح تقارن:</strong> یکسان کردن لب بالا و پایین</li>
|
||
<li><strong>هیدرتاسیون لب:</strong> مقدار کم برای صافی و رطوبت لب</li>
|
||
</ul>
|
||
<h2>روند انجام تزریق ژل لب</h2>
|
||
<ol>
|
||
<li>مشاوره و بررسی انتظارات</li>
|
||
<li>عکاسی قبل از عمل</li>
|
||
<li>اعمال کرم بیحسی ۳۰ دقیقه قبل</li>
|
||
<li>تزریق دقیق فیلر با سوزن ریز</li>
|
||
<li>ماساژ و شکلدهی</li>
|
||
<li>بررسی نهایی و عکاسی</li>
|
||
</ol>
|
||
<h2>نتایج طبیعی — چطور ممکن است؟</h2>
|
||
<p>کلید نتیجه طبیعی تزریق ژل لب، انتخاب مقدار مناسب و تزریق دقیق است. در مطب ما، رویکرد محافظهکارانه برای حفظ ظاهر طبیعی اولویت است. قانون طلایی: بهتر است بعداً حجم اضافه کنید تا اینکه بیش از حد تزریق کنید.</p>
|
||
<h2>مراقبتهای بعد از تزریق ژل لب</h2>
|
||
<ul>
|
||
<li>۲۴ ساعت اول از فعالیت سنگین بپرهیزید</li>
|
||
<li>از فشار دادن یا ماساژ ناحیه خودداری کنید</li>
|
||
<li>لبتان را هیدراته نگه دارید (کرم لب بدون عطر)</li>
|
||
<li>۴۸ ساعت از لیپاستیک دائمی بپرهیزید</li>
|
||
</ul>
|
||
<h2>نتیجهگیری</h2>
|
||
<p>تزریق ژل لب با دست پزشک مجرب میتواند زیبایی طبیعی لبها را چند برابر کند. برای مشاوره و رزرو نوبت با مطب دکتر سوسن آلطه تماس بگیرید.</p>"
|
||
}
|
||
);
|
||
await db.SaveChangesAsync();
|
||
}
|
||
|
||
// ── FAQs ──────────────────────────────────────────────────────────────────
|
||
if (!await db.Faqs.AnyAsync())
|
||
{
|
||
db.Faqs.AddRange(
|
||
new Faq { Question="بوتاکس چقدر ماندگاری دارد؟", Answer="اثرات بوتاکس معمولاً بین ۳ تا ۶ ماه پایدار است. با تکرار منظم تزریقها، ماندگاری افزایش مییابد زیرا عضلات به مرور زمان ضعیفتر میشوند.", Order=1 },
|
||
new Faq { Question="آیا تزریق فیلر دردناک است؟", Answer="با استفاده از کرم بیحسی موضعی که ۳۰ دقیقه قبل از تزریق اعمال میشود، درد به حداقل میرسد. اکثر بیماران تنها احساس فشار خفیفی میکنند.", Order=2 },
|
||
new Faq { Question="لیزر موهای زائد به چه تعداد جلسه نیاز دارد؟", Answer="برای نتایج مطلوب معمولاً ۶ تا ۸ جلسه نیاز است. فاصله جلسات بسته به ناحیه: صورت هر ۴-۶ هفته و بدن هر ۶-۸ هفته.", Order=3 },
|
||
new Faq { Question="آیا ملاسما (لک هورمونی) قابل درمان است؟", Answer="بله، با ترکیب درمانهای موضعی، پیلینگ شیمیایی و در موارد مقاوم لیزر، میتوان ملاسما را به طور قابل توجهی کاهش داد. ضروری است از ضدآفتاب روزانه استفاده شود.", Order=4 },
|
||
new Faq { Question="هایفو برای چه سنی مناسب است؟", Answer="هایفو برای افراد ۳۰ تا ۶۵ ساله که افتادگی خفیف تا متوسط پوست دارند ایدهآل است. بهترین نتایج را در افرادی که شروع به افتادگی پوست کردهاند مشاهده میکنیم.", Order=5 },
|
||
new Faq { Question="قبل از نوبت اول چه مدارکی نیاز است؟", Answer="برای اولین ویزیت نیازی به مدرک خاصی ندارید. لیست داروهایی که مصرف میکنید و سابقه بیماریهای پوستی خود را همراه داشته باشید.", Order=6 }
|
||
);
|
||
await db.SaveChangesAsync();
|
||
}
|
||
}
|