Add Gitea CI/CD for hamkadr.ir (Nexus build + self-hosted compose deploy)
- .gitea/workflows/ci-cd.yml: dotnet build via mirror.soroushasadi.com; self-hosted deploy with pg_dump backup, rollback tag, scoped recreate, /healthz wait, prune - Dockerfile (sdk/aspnet 10 via Nexus) + nuget.docker.config + .dockerignore - docker-compose.prod.yml: app on 127.0.0.1:APP_PORT, Postgres internal-only + named volume - deploy/nginx-hamkadr.ir.conf + DEPLOY.md (ports: 22/80/443 only; DB never exposed) - Prod seeds reference data only (no demo listings); ForwardedHeaders for nginx TLS; /healthz endpoint Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,12 +4,14 @@ using Microsoft.EntityFrameworkCore;
|
||||
namespace JobsMedical.Web.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Seeds a believable Tehran-focused board so the marketplace doesn't look empty on first run
|
||||
/// (the cold-start problem). Idempotent: only seeds when the DB is empty.
|
||||
/// Seeds reference data (cities, roles, districts) always, and a believable Tehran demo board
|
||||
/// (facilities/shifts/jobs/raw listings) only when <paramref name="includeDemo"/> is true.
|
||||
/// In production we pass false so real employers populate listings — no fake data goes public.
|
||||
/// Idempotent: reference seeds only when empty; demo seeds only when no facilities exist.
|
||||
/// </summary>
|
||||
public static class SeedData
|
||||
{
|
||||
public static async Task EnsureSeededAsync(AppDbContext db)
|
||||
public static async Task EnsureSeededAsync(AppDbContext db, bool includeDemo = true)
|
||||
{
|
||||
if (await db.Cities.AnyAsync()) return;
|
||||
|
||||
@@ -49,6 +51,9 @@ public static class SeedData
|
||||
new District { Name = "تجریش", CityId = tehran.Id });
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
// ----- Demo data (Tehran sample board): development only -----
|
||||
if (!includeDemo) return;
|
||||
|
||||
var facilities = new[]
|
||||
{
|
||||
new Facility { Name = "بیمارستان میلاد", Type = FacilityType.Hospital, CityId = tehran.Id,
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Text.Unicode;
|
||||
using JobsMedical.Web.Data;
|
||||
using JobsMedical.Web.Services;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
@@ -45,7 +46,8 @@ using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
db.Database.Migrate();
|
||||
await SeedData.EnsureSeededAsync(db);
|
||||
// Production seeds reference data only (no demo facilities/shifts); dev seeds the full board.
|
||||
await SeedData.EnsureSeededAsync(db, app.Environment.IsDevelopment());
|
||||
}
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
@@ -56,6 +58,16 @@ if (!app.Environment.IsDevelopment())
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
// Behind nginx (TLS terminated upstream): trust X-Forwarded-Proto/For so the app knows it's
|
||||
// HTTPS — required for correct secure cookies and to avoid HTTPS-redirect loops.
|
||||
var forwardedOptions = new ForwardedHeadersOptions
|
||||
{
|
||||
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
|
||||
};
|
||||
forwardedOptions.KnownIPNetworks.Clear(); // only nginx can reach the container's bound port
|
||||
forwardedOptions.KnownProxies.Clear();
|
||||
app.UseForwardedHeaders(forwardedOptions);
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseRouting();
|
||||
@@ -70,4 +82,7 @@ app.MapStaticAssets();
|
||||
app.MapRazorPages()
|
||||
.WithStaticAssets();
|
||||
|
||||
// Lightweight liveness probe for the deploy health-wait loop (and uptime checks).
|
||||
app.MapGet("/healthz", () => Results.Text("ok"));
|
||||
|
||||
app.Run();
|
||||
|
||||
Reference in New Issue
Block a user