ReleaseNote -> ChangeLog

This commit is contained in:
Sedat ÖZTÜRK 2026-08-17 11:51:46 +03:00
parent c993b77b5d
commit 9a2d88b93b
23 changed files with 258 additions and 258 deletions

4
.gitignore vendored
View file

@ -4,7 +4,7 @@ configs/**/data/**
**/.DS_Store
logs/
# Deploy sırasında git tag'lerinden üretilir (configs/release-notes/generate-release-notes.sh)
# Deploy sırasında git tag'lerinden üretilir (configs/change-logs/generate-change-logs.sh)
# ve API konteynerine mount edilir. Takip edilirse deploy'daki `git pull` yerel
# değişiklikle çakışır ve dosya eskir.
configs/release-notes/release-notes.json
configs/change-logs/change-logs.json

View file

@ -710,23 +710,23 @@ konfigürasyonu ile üretilir.
(`views/version/swRegistration.ts`, `sw.ts`, `AppUpdateDialog.tsx`).
- Isıtma yalnızca kuruluma bağlı değildir: uygulama açılışta `ENSURE_WARMUP` mesajı yollar,
service worker eksik kalan bileşenleri kaldığı yerden tamamlar.
- **Sürüm bilgisi backend'dedir.** `Sas_H_ReleaseNote` tablosu tek kaynaktır; istemci sürüm
- **Sürüm bilgisi backend'dedir.** `Sas_H_ChangeLog` tablosu tek kaynaktır; istemci sürüm
numarasını ve son sürümün notlarını `application-configuration` yanıtındaki
`extraProperties.changeLogs` alanından okur (`PlatformApplicationConfigurationContributor`),
tam listeyi ise `/api/app/release-note` endpoint'inden alır. Ayrı bir `version.json` isteği
tam listeyi ise `/api/app/change-log` endpoint'inden alır. Ayrı bir `version.json` isteği
yoktur. Changelog `/admin/changeLog` ekranında gösterilir; sürüm numarası son görülenden
farklıysa kullanıcı giriş sonrası oraya yönlendirilir (tetikleyici bundle değil, sürümdür —
yalnızca yeni bir git tag'i eklenmiş olsa da çalışır).
- **Sürüm notu akışı — DbMigrator gerektirmez:** `configs/release-notes/generate-release-notes.sh`
deploy sırasında (`deploy/app.sh`) git tag'lerinden `configs/release-notes/release-notes.json`
üretir, dosya API konteynerine `/etc/api/release-notes` altına salt okunur mount edilir ve
`ReleaseNoteStartupService` uygulama açılışında tabloyu bu dosyayla eşitler. Dosya imaja
- **Sürüm notu akışı — DbMigrator gerektirmez:** `configs/change-logs/generate-change-logs.sh`
deploy sırasında (`deploy/app.sh`) git tag'lerinden `configs/change-logs/change-logs.json`
üretir, dosya API konteynerine `/etc/api/change-logs` altına salt okunur mount edilir ve
`ChangeLogStartupService` uygulama açılışında tabloyu bu dosyayla eşitler. Dosya imaja
**gömülmez**: api build context'i içindeyken her yeni tag Dockerfile'daki `COPY . .`
katmanını geçersiz kılıp imajı gereksiz yere yeniden derletiyordu; artık kod değişmediyse
imaj cache'ten gelir. Yeni sürüm için tag atıp deploy etmek yeterlidir. Notlar
**annotated** tag mesajından okunur (`git tag -a 1.2.0 -m "..."`), her satır bir madde.
- **Tablo git tag listesinin aynasıdır:** yeni tag eklenir, mesajı düzeltilen tag güncellenir,
git'ten silinen tag tablodan da (hard delete ile) kaldırılır. Bu yüzden `Sas_H_ReleaseNote`
git'ten silinen tag tablodan da (hard delete ile) kaldırılır. Bu yüzden `Sas_H_ChangeLog`
elle düzenlenmemelidir — ilk deploy'da geri alınır. Dosya okunamaz veya boşsa senkronizasyon
hiçbir şey yapmaz, tablo boşaltılmaz.
- Deploy algılaması service worker'ın kendi `registration.update()` kontrolüne dayanır:

View file

@ -1,9 +1,9 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Sozsoft.Platform.ReleaseNotes;
namespace Sozsoft.Platform.ChangeLogs;
public class ReleaseNoteDto : EntityDto<Guid>
public class ChangeLogDto : EntityDto<Guid>
{
public string Version { get; set; }
public DateTime ReleaseDate { get; set; }

View file

@ -6,34 +6,34 @@ using Microsoft.AspNetCore.Authorization;
using Sozsoft.Platform.Entities;
using Volo.Abp.Domain.Repositories;
namespace Sozsoft.Platform.ReleaseNotes;
namespace Sozsoft.Platform.ChangeLogs;
/// <summary>
/// Sürüm notları (ChangeLog ekranının kaynağı).
///
/// Kayıtlar git tag'lerinden üretilip <c>Sas_H_ReleaseNote</c> tablosuna
/// Kayıtlar git tag'lerinden üretilip <c>Sas_H_ChangeLog</c> tablosuna
/// tohumlanır. Uygulamanın çalışan sürüm numarası ise application-configuration
/// yanıtındaki <c>extraProperties.changeLogs</c> ile gelir; bu servis yalnızca
/// tam listeyi döner, böylece her config isteğinde tüm changelog taşınmaz.
/// </summary>
[Authorize]
public class ReleaseNoteAppService : PlatformAppService
public class ChangeLogAppService : PlatformAppService
{
private readonly IRepository<ReleaseNote, Guid> _releaseNoteRepository;
private readonly IRepository<ChangeLog, Guid> _changeLogRepository;
public ReleaseNoteAppService(IRepository<ReleaseNote, Guid> releaseNoteRepository)
public ChangeLogAppService(IRepository<ChangeLog, Guid> changeLogRepository)
{
_releaseNoteRepository = releaseNoteRepository;
_changeLogRepository = changeLogRepository;
}
public async Task<List<ReleaseNoteDto>> GetListAsync()
public async Task<List<ChangeLogDto>> GetListAsync()
{
var items = await _releaseNoteRepository.GetListAsync(x => x.IsPublished);
var items = await _changeLogRepository.GetListAsync(x => x.IsPublished);
return items
.OrderByDescending(note => note.ReleaseDate)
.ThenByDescending(note => note.Version)
.Select(note => new ReleaseNoteDto
.Select(note => new ChangeLogDto
{
Id = note.Id,
Version = note.Version,

View file

@ -98,5 +98,5 @@ public enum TableNameEnum
DbTableColumn,
DevRequest,
Agenda,
ReleaseNote
ChangeLog
}

View file

@ -15,7 +15,7 @@ public static class TableNameResolver
{ nameof(TableNameEnum.DbTableColumn), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.LogEntry), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.AiBot), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.ReleaseNote), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.ChangeLog), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.GlobalSearch), (TablePrefix.TenantByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.ContactTitle), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.Currency), (TablePrefix.PlatformByName, MenuPrefix.Saas) },

View file

@ -9,12 +9,12 @@ namespace Sozsoft.Platform.Entities;
/// changelog'u application-configuration yanıtındaki
/// <c>extraProperties.changeLogs</c> alanından okur, ayrı bir sürüm dosyası yoktur.
///
/// Kayıtlar git tag'lerinden türer ve açılışta <c>ReleaseNoteStartupService</c>
/// Kayıtlar git tag'lerinden türer ve açılışta <c>ChangeLogStartupService</c>
/// tarafından tag listesiyle birebir eşitlenir.
///
/// Host seviyesindedir (tenant'a göre değişmez).
/// </summary>
public class ReleaseNote : FullAuditedEntity<Guid>
public class ChangeLog : FullAuditedEntity<Guid>
{
/// <summary>Kullanıcıya gösterilen sürüm numarası (ör. "1.2.1").</summary>
public string Version { get; set; }
@ -46,11 +46,11 @@ public class ReleaseNote : FullAuditedEntity<Guid>
.ToArray();
}
protected ReleaseNote()
protected ChangeLog()
{
}
public ReleaseNote(string version, DateTime releaseDate, string notes, string commit = null, bool isPublished = true)
public ChangeLog(string version, DateTime releaseDate, string notes, string commit = null, bool isPublished = true)
{
Version = version;
ReleaseDate = releaseDate;

View file

@ -46,7 +46,7 @@ public class PlatformDbContext :
public DbSet<Tenant> Tenants { get; set; }
public DbSet<TenantConnectionString> TenantConnectionStrings { get; set; }
public DbSet<AiBot> AiBots { get; set; }
public DbSet<ReleaseNote> ReleaseNotes { get; set; }
public DbSet<ChangeLog> ChangeLogs { get; set; }
public DbSet<Branch> Branches { get; set; }
public DbSet<BranchUsers> BranchUsers { get; set; }
public DbSet<GlobalSearch> GlobalSearchs { get; set; }
@ -203,9 +203,9 @@ public class PlatformDbContext :
b.HasIndex(x => new { x.Name }).IsUnique().HasFilter(null);
});
builder.Entity<ReleaseNote>(b =>
builder.Entity<ChangeLog>(b =>
{
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.ReleaseNote)), Prefix.DbSchema);
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.ChangeLog)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(a => a.Version).IsRequired().HasMaxLength(32);

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Sozsoft.Platform.Migrations
{
[DbContext(typeof(PlatformDbContext))]
[Migration("20260812194642_Initial")]
[Migration("20260817083025_Initial")]
partial class Initial
{
/// <inheritdoc />
@ -1381,6 +1381,68 @@ namespace Sozsoft.Platform.Migrations
b.ToTable("Sas_T_BranchUsers", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.ChangeLog", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("Commit")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<bool>("IsPublished")
.HasColumnType("bit");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("Notes")
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("ReleaseDate")
.HasColumnType("datetime2");
b.Property<string>("Version")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
b.HasKey("Id");
b.HasIndex("Version")
.IsUnique()
.HasFilter("[IsDeleted] = 0");
b.ToTable("Sas_H_ChangeLog", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.City", b =>
{
b.Property<Guid>("Id")
@ -4662,68 +4724,6 @@ namespace Sozsoft.Platform.Migrations
b.ToTable("Sas_H_Product", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.ReleaseNote", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("Commit")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<bool>("IsPublished")
.HasColumnType("bit");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("Notes")
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("ReleaseDate")
.HasColumnType("datetime2");
b.Property<string>("Version")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
b.HasKey("Id");
b.HasIndex("Version")
.IsUnique()
.HasFilter("[IsDeleted] = 0");
b.ToTable("Sas_H_ReleaseNote", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.ReportCategory", b =>
{
b.Property<Guid>("Id")

View file

@ -1035,6 +1035,29 @@ namespace Sozsoft.Platform.Migrations
table.PrimaryKey("PK_Sas_H_BlogCategory", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Sas_H_ChangeLog",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Version = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
ReleaseDate = table.Column<DateTime>(type: "datetime2", nullable: false),
Commit = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
Notes = table.Column<string>(type: "nvarchar(max)", nullable: true),
IsPublished = table.Column<bool>(type: "bit", nullable: false),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Sas_H_ChangeLog", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Sas_H_Contact",
columns: table => new
@ -1700,29 +1723,6 @@ namespace Sozsoft.Platform.Migrations
table.PrimaryKey("PK_Sas_H_Product", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Sas_H_ReleaseNote",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Version = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
ReleaseDate = table.Column<DateTime>(type: "datetime2", nullable: false),
Commit = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
Notes = table.Column<string>(type: "nvarchar(max)", nullable: true),
IsPublished = table.Column<bool>(type: "bit", nullable: false),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Sas_H_ReleaseNote", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Sas_H_Route",
columns: table => new
@ -4057,6 +4057,13 @@ namespace Sozsoft.Platform.Migrations
unique: true,
filter: "[IsDeleted] = 0");
migrationBuilder.CreateIndex(
name: "IX_Sas_H_ChangeLog_Version",
table: "Sas_H_ChangeLog",
column: "Version",
unique: true,
filter: "[IsDeleted] = 0");
migrationBuilder.CreateIndex(
name: "IX_Sas_H_City_Country_Name",
table: "Sas_H_City",
@ -4257,13 +4264,6 @@ namespace Sozsoft.Platform.Migrations
unique: true,
filter: "[IsDeleted] = 0");
migrationBuilder.CreateIndex(
name: "IX_Sas_H_ReleaseNote_Version",
table: "Sas_H_ReleaseNote",
column: "Version",
unique: true,
filter: "[IsDeleted] = 0");
migrationBuilder.CreateIndex(
name: "IX_Sas_H_Route_Key",
table: "Sas_H_Route",
@ -4526,6 +4526,9 @@ namespace Sozsoft.Platform.Migrations
migrationBuilder.DropTable(
name: "Sas_H_BlogPost");
migrationBuilder.DropTable(
name: "Sas_H_ChangeLog");
migrationBuilder.DropTable(
name: "Sas_H_Contact");
@ -4604,9 +4607,6 @@ namespace Sozsoft.Platform.Migrations
migrationBuilder.DropTable(
name: "Sas_H_Product");
migrationBuilder.DropTable(
name: "Sas_H_ReleaseNote");
migrationBuilder.DropTable(
name: "Sas_H_Route");

View file

@ -1378,6 +1378,68 @@ namespace Sozsoft.Platform.Migrations
b.ToTable("Sas_T_BranchUsers", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.ChangeLog", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("Commit")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<bool>("IsPublished")
.HasColumnType("bit");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("Notes")
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("ReleaseDate")
.HasColumnType("datetime2");
b.Property<string>("Version")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
b.HasKey("Id");
b.HasIndex("Version")
.IsUnique()
.HasFilter("[IsDeleted] = 0");
b.ToTable("Sas_H_ChangeLog", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.City", b =>
{
b.Property<Guid>("Id")
@ -4659,68 +4721,6 @@ namespace Sozsoft.Platform.Migrations
b.ToTable("Sas_H_Product", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.ReleaseNote", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("Commit")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<bool>("IsPublished")
.HasColumnType("bit");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("Notes")
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("ReleaseDate")
.HasColumnType("datetime2");
b.Property<string>("Version")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
b.HasKey("Id");
b.HasIndex("Version")
.IsUnique()
.HasFilter("[IsDeleted] = 0");
b.ToTable("Sas_H_ReleaseNote", (string)null);
});
modelBuilder.Entity("Sozsoft.Platform.Entities.ReportCategory", b =>
{
b.Property<Guid>("Id")

View file

@ -17,7 +17,7 @@ using Volo.Abp.Uow;
namespace Sozsoft.Platform.Extensions;
public class ReleaseNoteSeedDto
public class ChangeLogSeedDto
{
public string Version { get; set; }
public string ReleaseDate { get; set; }
@ -25,16 +25,16 @@ public class ReleaseNoteSeedDto
public List<string> Notes { get; set; }
}
public class ReleaseNoteSeedFileDto
public class ChangeLogSeedFileDto
{
public List<ReleaseNoteSeedDto> ReleaseNotes { get; set; }
public List<ChangeLogSeedDto> ChangeLogs { get; set; }
}
/// <summary>
/// Sürüm notlarını uygulama açılışında veritabanına senkronize eder.
///
/// Kaynak, konteynere mount edilen <c>release-notes.json</c> dosyasıdır; bu dosya
/// deploy sırasında <c>configs/release-notes/generate-release-notes.sh</c> tarafından
/// Kaynak, konteynere mount edilen <c>change-logs.json</c> dosyasıdır; bu dosya
/// deploy sırasında <c>configs/change-logs/generate-change-logs.sh</c> tarafından
/// git tag'lerinden üretilir. Böylece yeni bir sürüm tag'i eklemek için DbMigrator
/// çalıştırmak GEREKMEZ — API'yi deploy etmek yeterlidir.
///
@ -45,25 +45,25 @@ public class ReleaseNoteSeedFileDto
/// tag güncellenir, silinen tag veritabanından da kaldırılır. Dosya okunamaz veya
/// boşsa hiçbir şey yapılmaz (yanlışlıkla tablo boşaltılmasın diye).
/// </summary>
public class ReleaseNoteStartupService : IHostedService
public class ChangeLogStartupService : IHostedService
{
private const string FileName = "release-notes.json";
private const string FileName = "change-logs.json";
// Konteynerde docker-compose-app.yml ile mount edilen yol.
private const string MountedDirectory = "/etc/api/release-notes";
private const string MountedDirectory = "/etc/api/change-logs";
private readonly IServiceProvider _serviceProvider;
private readonly IHostApplicationLifetime _applicationLifetime;
private readonly IHostEnvironment _environment;
private readonly IConfiguration _configuration;
private readonly ILogger<ReleaseNoteStartupService> _logger;
private readonly ILogger<ChangeLogStartupService> _logger;
public ReleaseNoteStartupService(
public ChangeLogStartupService(
IServiceProvider serviceProvider,
IHostApplicationLifetime applicationLifetime,
IHostEnvironment environment,
IConfiguration configuration,
ILogger<ReleaseNoteStartupService> logger)
ILogger<ChangeLogStartupService> logger)
{
_serviceProvider = serviceProvider;
_applicationLifetime = applicationLifetime;
@ -105,11 +105,11 @@ public class ReleaseNoteStartupService : IHostedService
try
{
await using var stream = File.OpenRead(path);
var data = await JsonSerializer.DeserializeAsync<ReleaseNoteSeedFileDto>(
var data = await JsonSerializer.DeserializeAsync<ChangeLogSeedFileDto>(
stream,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
var items = data?.ReleaseNotes ?? [];
var items = data?.ChangeLogs ?? [];
if (items.Count == 0)
{
return;
@ -145,13 +145,13 @@ public class ReleaseNoteStartupService : IHostedService
}
/// <summary>
/// Dosya yolunu sırayla dener: açık ayar (<c>App:ReleaseNotesPath</c>), konteynerdeki
/// mount noktası, son olarak yerel geliştirmede depodaki <c>configs/release-notes</c>
/// Dosya yolunu sırayla dener: açık ayar (<c>App:ChangeLogsPath</c>), konteynerdeki
/// mount noktası, son olarak yerel geliştirmede depodaki <c>configs/change-logs</c>
/// klasörü. Hiçbiri yoksa null döner.
/// </summary>
private string ResolvePath()
{
var configured = _configuration["App:ReleaseNotesPath"];
var configured = _configuration["App:ChangeLogsPath"];
if (!string.IsNullOrWhiteSpace(configured))
{
// Klasör verildiyse dosya adı eklenir.
@ -166,11 +166,11 @@ public class ReleaseNoteStartupService : IHostedService
}
// Yerel geliştirme: ContentRootPath api/src/Sozsoft.Platform.HttpApi.Host,
// aranan yol depo kökündeki configs/release-notes.
// aranan yol depo kökündeki configs/change-logs.
var directory = new DirectoryInfo(_environment.ContentRootPath);
while (directory != null)
{
var candidate = Path.Combine(directory.FullName, "configs", "release-notes", FileName);
var candidate = Path.Combine(directory.FullName, "configs", "change-logs", FileName);
if (File.Exists(candidate))
{
return candidate;
@ -184,9 +184,9 @@ public class ReleaseNoteStartupService : IHostedService
private static async Task<(int Inserted, int Updated, int Deleted)> SyncItemsAsync(
IServiceProvider services,
List<ReleaseNoteSeedDto> items)
List<ChangeLogSeedDto> items)
{
var repository = services.GetRequiredService<IRepository<ReleaseNote, Guid>>();
var repository = services.GetRequiredService<IRepository<ChangeLog, Guid>>();
var unitOfWorkManager = services.GetRequiredService<IUnitOfWorkManager>();
var currentTenant = services.GetRequiredService<ICurrentTenant>();
@ -209,7 +209,7 @@ public class ReleaseNoteStartupService : IHostedService
if (!existing.TryGetValue(item.Version, out var current))
{
await repository.InsertAsync(new ReleaseNote(item.Version, releaseDate, notes, item.Commit));
await repository.InsertAsync(new ChangeLog(item.Version, releaseDate, notes, item.Commit));
inserted++;
continue;
}

View file

@ -27,12 +27,12 @@ public class PlatformApplicationConfigurationContributor : IApplicationConfigura
// Uygulamanın çalışan sürümü: istemcinin tek sürüm kaynağı (ayrı bir
// version.json isteği yoktur). Yalnızca EN SON sürüm taşınır; tam liste
// ChangeLog ekranının kendi endpoint'inden (/api/app/release-note) gelir,
// ChangeLog ekranının kendi endpoint'inden (/api/app/change-log) gelir,
// aksi halde her config isteğinde bütün changelog gönderilirdi.
var releaseNoteRepository = context.ServiceProvider.GetRequiredService<IRepository<ReleaseNote, Guid>>();
var releaseNotes = await releaseNoteRepository.GetListAsync(x => x.IsPublished);
var changeLogRepository = context.ServiceProvider.GetRequiredService<IRepository<ChangeLog, Guid>>();
var changeLogs = await changeLogRepository.GetListAsync(x => x.IsPublished);
var latest = releaseNotes
var latest = changeLogs
.OrderByDescending(note => note.ReleaseDate)
.ThenByDescending(note => note.Version)
.FirstOrDefault();

View file

@ -140,9 +140,9 @@ public class PlatformHttpApiHostModule : AbpModule
ConfigureCache();
ConfigureHangfire(context, configuration);
context.Services.AddHostedService<BackgroundWorkerStartupService>();
// Sürüm notları imaja gömülü release-notes.json'dan senkronize edilir;
// Sürüm notları imaja gömülü change-logs.json'dan senkronize edilir;
// yeni bir sürüm tag'i için DbMigrator çalıştırmak gerekmez.
context.Services.AddHostedService<Extensions.ReleaseNoteStartupService>();
context.Services.AddHostedService<Extensions.ChangeLogStartupService>();
ConfigureBlobStoring(configuration);
ConfigureAuditing();

View file

@ -54,8 +54,8 @@
<None Remove="Logs\**" />
</ItemGroup>
<!-- Sürüm notları (release-notes.json) artık bu projeye kopyalanmaz: dosya
configs/release-notes altında üretilir ve konteynere mount edilir. api build
<!-- Sürüm notları (change-logs.json) artık bu projeye kopyalanmaz: dosya
configs/change-logs altında üretilir ve konteynere mount edilir. api build
context'i içindeyken her yeni tag imajı gereksiz yere yeniden derletiyordu. -->
</Project>

View file

@ -0,0 +1,18 @@
# change-logs
`change-logs.json` git tag'lerinden üretilir ve API konteynerine salt okunur
mount edilir (`/etc/api/change-logs`). İmaja gömülmez.
- Üretim: `bash configs/change-logs/generate-change-logs.sh`
- Deploy akışında otomatik çağrılır: `configs/deployment/scripts/deploy/app.sh`
- Okuyan taraf: `ChangeLogStartupService` (uygulama açılışında `Sas_H_ChangeLog`
tablosunu bu dosyayla eşitler)
Bu klasör API build context'inin dışındadır; böylece yeni bir tag atmak API imajını
yeniden derletmez, docker cache'i bozulmaz.
Farklı bir yol kullanılacaksa `App:ChangeLogsPath` (env: `App__ChangeLogsPath`)
ayarı ile ezilebilir.
`change-logs.json` `.gitignore`'dadır; deploy'daki `git pull` ile çakışmaması için
takip edilmez.

View file

@ -2,11 +2,11 @@
set -e
# Sürüm notlarını git tag'lerinden üretir:
# configs/release-notes/release-notes.json
# configs/change-logs/change-logs.json
#
# Dosya İMAJA GÖMÜLMEZ. Konteynere docker-compose-app.yml üzerinden
# `/etc/api/release-notes` altına salt okunur mount edilir; ReleaseNoteStartupService
# açılışta bu yolu okuyup `Sas_H_ReleaseNote` tablosuyla eşitler, DbMigrator gerekmez.
# `/etc/api/change-logs` altına salt okunur mount edilir; ChangeLogStartupService
# açılışta bu yolu okuyup `Sas_H_ChangeLog` tablosuyla eşitler, DbMigrator gerekmez.
#
# Neden api/ altında değil: dosya api build context'i içindeyken her yeni tag
# Dockerfile'daki `COPY . .` katmanını geçersiz kılıyor ve kodda hiçbir değişiklik
@ -21,14 +21,14 @@ set -e
# Dış bağımlılığı yoktur (jq/node gerekmez): JSON kaçışları sed ile yapılır.
cd "$(dirname "$0")"
OUTPUT="release-notes.json"
OUTPUT="change-logs.json"
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "> git deposu yok, ${OUTPUT} güncellenmedi." >&2
exit 0
fi
echo "> release-notes.json oluşturuluyor..."
echo "> change-logs.json oluşturuluyor..."
# origin'de olmayan yerel tag'ler sürüm sırasını bozar; temizle. `--force` şart:
# yerel bir tag origin'dekinden farklı commit'e bakıyorsa git onu "would clobber
@ -49,7 +49,7 @@ escape_json() {
}
TMP=$(mktemp)
printf '{\n "ReleaseNotes": [\n' > "$TMP"
printf '{\n "ChangeLogs": [\n' > "$TMP"
FIRST_TAG=true
for TAG in $TAGS; do

View file

@ -45,9 +45,9 @@ services:
- ../seeds:/etc/api/seeds
- api-keys:/root/.aspnet/DataProtection-Keys
# Surum notlari imaja gomulmez; deploy sirasinda git tag'lerinden uretilip
# buradan okunur (ReleaseNoteStartupService). Boylece yeni tag API imajini
# buradan okunur (ChangeLogStartupService). Boylece yeni tag API imajini
# yeniden derletmez.
- ../release-notes:/etc/api/release-notes:ro
- ../change-logs:/etc/api/change-logs:ro
networks:
- db
- default

View file

@ -9,7 +9,7 @@ cd ~/sozsoft-platform/api
# Sürüm notları burada ÜRETİLMEZ. Dosya api build context'i içindeyken her yeni
# tag Dockerfile'daki `COPY . .` katmanını geçersiz kılıyor ve kodda hiçbir
# değişiklik olmasa bile imaj baştan derleniyordu. Artık configs/release-notes
# değişiklik olmasa bile imaj baştan derleniyordu. Artık configs/change-logs
# altında deploy sırasında üretilip konteynere mount ediliyor
# (configs/deployment/scripts/deploy/app.sh).

View file

@ -14,14 +14,14 @@ git checkout main
# farklı commit'e bakıyorsa --force olmadan reddedilir ve yeni tag'ler gelmez.
git fetch --prune --prune-tags --tags --force && git pull
# Sürüm notları imaja gömülü değil; konteynere configs/release-notes klasöründen
# Sürüm notları imaja gömülü değil; konteynere configs/change-logs klasöründen
# mount ediliyor ve API açılışta veritabanına senkronize ediyor (DbMigrator
# gerekmez). `bash ...` ile çağrılır: depo Windows'tan yönetildiği için
# çalıştırma biti kaybolunca "Permission denied" verip notları sessizce düşürüyordu.
RELEASE_NOTES=~/sozsoft-platform/configs/release-notes/release-notes.json
bash ~/sozsoft-platform/configs/release-notes/generate-release-notes.sh
RELEASE_NOTES=~/sozsoft-platform/configs/change-logs/change-logs.json
bash ~/sozsoft-platform/configs/change-logs/generate-change-logs.sh
# Dosya yoksa API açılışta "release-notes.json bulunamadı" uyarısı verir ve
# Dosya yoksa API açılışta "change-logs.json bulunamadı" uyarısı verir ve
# changelog eskir; sessizce devam etmek yerine burada dur.
if [ ! -s "$RELEASE_NOTES" ]; then
echo "HATA: ${RELEASE_NOTES} üretilemedi, deploy durduruldu." >&2

View file

@ -1,18 +0,0 @@
# release-notes
`release-notes.json` git tag'lerinden üretilir ve API konteynerine salt okunur
mount edilir (`/etc/api/release-notes`). İmaja gömülmez.
- Üretim: `bash configs/release-notes/generate-release-notes.sh`
- Deploy akışında otomatik çağrılır: `configs/deployment/scripts/deploy/app.sh`
- Okuyan taraf: `ReleaseNoteStartupService` (uygulama açılışında `Sas_H_ReleaseNote`
tablosunu bu dosyayla eşitler)
Bu klasör API build context'inin dışındadır; böylece yeni bir tag atmak API imajını
yeniden derletmez, docker cache'i bozulmaz.
Farklı bir yol kullanılacaksa `App:ReleaseNotesPath` (env: `App__ReleaseNotesPath`)
ayarı ile ezilebilir.
`release-notes.json` `.gitignore`'dadır; deploy'daki `git pull` ile çakışmaması için
takip edilmez.

View file

@ -1,25 +1,25 @@
import apiService from '@/services/api.service'
/** `Sas_H_ReleaseNote` kaydı; git tag'lerinden tohumlanır. */
export interface ReleaseNoteDto {
/** `Sas_H_ChangeLog` kaydı; git tag'lerinden tohumlanır. */
export interface ChangeLogDto {
id: string
version: string
releaseDate: string
changeLog: string[]
}
class ReleaseNoteService {
class ChangeLogService {
/**
* ChangeLog ekranının kaynağı. Uygulamanın çalışan sürüm numarası
* application-configuration ile geldiği için burada yalnızca tam liste alınır.
*/
async getList(): Promise<ReleaseNoteDto[]> {
const response = await apiService.fetchData<ReleaseNoteDto[]>({
url: '/api/app/release-note',
async getList(): Promise<ChangeLogDto[]> {
const response = await apiService.fetchData<ChangeLogDto[]>({
url: '/api/app/change-log',
method: 'GET',
})
return response.data
}
}
export const releaseNoteService = new ReleaseNoteService()
export const changeLogService = new ChangeLogService()

View file

@ -5,7 +5,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
import React, { useEffect, useState } from 'react'
import PageTitle from '@/components/shared/PageTitle'
import { FaRegCheckCircle, FaSyncAlt, FaTag } from 'react-icons/fa'
import { releaseNoteService, type ReleaseNoteDto } from '@/services/releaseNote.service'
import { changeLogService, type ChangeLogDto } from '@/services/changeLog.service'
import { checkForAppUpdate } from './swRegistration'
import { useStoreActions } from '@/store'
@ -96,16 +96,16 @@ const Changelog = () => {
const { isCheckingUpdate, checkForUpdate } = useManualUpdateCheck()
const clearPendingChangelog = useStoreActions((actions) => actions.locale.clearPendingChangelog)
// Kaynak `ReleaseNote` entity'sidir; liste yalnızca bu ekran açıldığında
// Kaynak `ChangeLog` entity'sidir; liste yalnızca bu ekran açıldığında
// istenir (application-configuration sadece çalışan sürümü taşır).
const [releases, setReleases] = useState<ReleaseNoteDto[]>([])
const [releases, setReleases] = useState<ChangeLogDto[]>([])
useEffect(() => {
// Bekleyen changelog bayrağı burada tüketilir; kullanıcı değişiklikleri
// gördüğü için sonraki girişlerde tekrar yönlendirilmez.
clearPendingChangelog()
void releaseNoteService
void changeLogService
.getList()
.then(setReleases)
.catch((error: unknown) => console.warn('Sürüm notları alınamadı.', error))