Developer Kits düzenlemesi
This commit is contained in:
parent
2ec7f085e4
commit
a0fa1b7e2b
13 changed files with 314 additions and 104 deletions
|
|
@ -11,8 +11,8 @@ public class CustomEntityDto : FullAuditedEntityDto<Guid>
|
||||||
public string TableName { get; set; } = string.Empty;
|
public string TableName { get; set; } = string.Empty;
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
public bool HasAuditFields { get; set; } = true;
|
public bool IsFullAuditedEntity { get; set; } = true;
|
||||||
public bool HasSoftDelete { get; set; } = true;
|
public bool IsMultiTenant { get; set; } = false;
|
||||||
public string MigrationStatus { get; set; } = "pending";
|
public string MigrationStatus { get; set; } = "pending";
|
||||||
public Guid? MigrationId { get; set; }
|
public Guid? MigrationId { get; set; }
|
||||||
public string EndpointStatus { get; set; } = "pending";
|
public string EndpointStatus { get; set; } = "pending";
|
||||||
|
|
@ -27,8 +27,8 @@ public class CreateUpdateCustomEntityDto
|
||||||
public string TableName { get; set; } = string.Empty;
|
public string TableName { get; set; } = string.Empty;
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
public bool HasAuditFields { get; set; } = true;
|
public bool IsFullAuditedEntity { get; set; } = true;
|
||||||
public bool HasSoftDelete { get; set; } = true;
|
public bool IsMultiTenant { get; set; } = false;
|
||||||
|
|
||||||
public List<CreateUpdateCustomEntityFieldDto> Fields { get; set; } = [];
|
public List<CreateUpdateCustomEntityFieldDto> Fields { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,9 +96,20 @@ public class CrudMigrationAppService : CrudAppService<
|
||||||
if (entity == null)
|
if (entity == null)
|
||||||
throw new EntityNotFoundException($"CustomEntity with id {entityId} not found");
|
throw new EntityNotFoundException($"CustomEntity with id {entityId} not found");
|
||||||
|
|
||||||
var fileName = $"{DateTime.UtcNow:yyyyMMddHHmmss}_Create{entity.Name}Table.sql";
|
// Check if there are any previously applied migrations for this entity
|
||||||
|
var migrationQueryable = await _migrationRepository.GetQueryableAsync();
|
||||||
|
var lastAppliedMigration = await migrationQueryable
|
||||||
|
.Where(m => m.EntityId == entityId && m.Status == "applied")
|
||||||
|
.OrderByDescending(m => m.CreationTime)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
var sqlScript = GenerateSqlScript(entity);
|
// Use "Update" if migrations were applied before, otherwise "Create"
|
||||||
|
var operationType = lastAppliedMigration != null ? "Update" : "Create";
|
||||||
|
var fileName = $"{DateTime.UtcNow:yyyyMMddHHmmss}_{operationType}{entity.Name}Table.sql";
|
||||||
|
|
||||||
|
var sqlScript = lastAppliedMigration != null
|
||||||
|
? GenerateAlterTableScript(entity, lastAppliedMigration)
|
||||||
|
: GenerateCreateTableScript(entity);
|
||||||
|
|
||||||
var migration = new CrudMigration
|
var migration = new CrudMigration
|
||||||
{
|
{
|
||||||
|
|
@ -118,7 +129,7 @@ public class CrudMigrationAppService : CrudAppService<
|
||||||
return ObjectMapper.Map<CrudMigration, CrudMigrationDto>(migration);
|
return ObjectMapper.Map<CrudMigration, CrudMigrationDto>(migration);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GenerateSqlScript(CustomEntity entity)
|
private string GenerateCreateTableScript(CustomEntity entity)
|
||||||
{
|
{
|
||||||
if (entity.Fields == null || !entity.Fields.Any())
|
if (entity.Fields == null || !entity.Fields.Any())
|
||||||
throw new InvalidOperationException($"Entity '{entity.Name}' does not have any fields defined.");
|
throw new InvalidOperationException($"Entity '{entity.Name}' does not have any fields defined.");
|
||||||
|
|
@ -156,19 +167,22 @@ public class CrudMigrationAppService : CrudAppService<
|
||||||
sb.AppendLine(",");
|
sb.AppendLine(",");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity.HasAuditFields)
|
// Add TenantId if entity is multi-tenant
|
||||||
|
if (entity.IsMultiTenant)
|
||||||
|
{
|
||||||
|
sb.AppendLine(" [TenantId] UNIQUEIDENTIFIER NULL,");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Full Audited Entity fields (includes both audit and soft delete)
|
||||||
|
if (entity.IsFullAuditedEntity)
|
||||||
{
|
{
|
||||||
sb.AppendLine(" [CreationTime] DATETIME2 DEFAULT SYSUTCDATETIME() NOT NULL,");
|
sb.AppendLine(" [CreationTime] DATETIME2 DEFAULT SYSUTCDATETIME() NOT NULL,");
|
||||||
sb.AppendLine(" [CreatorId] UNIQUEIDENTIFIER NULL,");
|
sb.AppendLine(" [CreatorId] UNIQUEIDENTIFIER NULL,");
|
||||||
sb.AppendLine(" [LastModificationTime] DATETIME2 DEFAULT SYSUTCDATETIME() NOT NULL,");
|
sb.AppendLine(" [LastModificationTime] DATETIME2 NULL,");
|
||||||
sb.AppendLine(" [LastModifierId] UNIQUEIDENTIFIER NULL,");
|
sb.AppendLine(" [LastModifierId] UNIQUEIDENTIFIER NULL,");
|
||||||
}
|
|
||||||
|
|
||||||
if (entity.HasSoftDelete)
|
|
||||||
{
|
|
||||||
sb.AppendLine(" [IsDeleted] BIT DEFAULT 0 NOT NULL,");
|
sb.AppendLine(" [IsDeleted] BIT DEFAULT 0 NOT NULL,");
|
||||||
sb.AppendLine(" [DeleterId] UNIQUEIDENTIFIER NULL,");
|
sb.AppendLine(" [DeleterId] UNIQUEIDENTIFIER NULL,");
|
||||||
sb.AppendLine(" [DeletionTime] DATETIME2 DEFAULT SYSUTCDATETIME() NOT NULL,");
|
sb.AppendLine(" [DeletionTime] DATETIME2 NULL,");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove last comma
|
// Remove last comma
|
||||||
|
|
@ -178,6 +192,87 @@ public class CrudMigrationAppService : CrudAppService<
|
||||||
return script;
|
return script;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GenerateAlterTableScript(CustomEntity entity, CrudMigration lastAppliedMigration)
|
||||||
|
{
|
||||||
|
if (entity.Fields == null || !entity.Fields.Any())
|
||||||
|
throw new InvalidOperationException($"Entity '{entity.Name}' does not have any fields defined.");
|
||||||
|
|
||||||
|
// Parse the last applied migration's SQL to understand what fields existed
|
||||||
|
var existingFieldNames = ExtractFieldNamesFromCreateScript(lastAppliedMigration.SqlScript);
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.AppendLine($@"/*
|
||||||
|
# Update {entity.DisplayName} table
|
||||||
|
|
||||||
|
1. Alter Table
|
||||||
|
- [{entity.TableName}]
|
||||||
|
- Adding new fields or modifying existing ones");
|
||||||
|
|
||||||
|
sb.AppendLine("*/\n");
|
||||||
|
|
||||||
|
// Find new fields that need to be added
|
||||||
|
var newFields = entity.Fields.Where(f => !existingFieldNames.Contains(f.Name)).ToList();
|
||||||
|
|
||||||
|
if (newFields.Any())
|
||||||
|
{
|
||||||
|
foreach (var field in newFields)
|
||||||
|
{
|
||||||
|
var sqlType = GetSqlType(field.Type, field.MaxLength);
|
||||||
|
sb.Append($"IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[{entity.TableName}]') AND name = '{field.Name}')\n");
|
||||||
|
sb.Append($"BEGIN\n");
|
||||||
|
sb.Append($" ALTER TABLE [{entity.TableName}] ADD [{field.Name}] {sqlType}");
|
||||||
|
|
||||||
|
if (field.IsRequired)
|
||||||
|
sb.Append(" NOT NULL");
|
||||||
|
else
|
||||||
|
sb.Append(" NULL");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(field.DefaultValue))
|
||||||
|
sb.Append($" DEFAULT {FormatDefaultValue(field.Type, field.DefaultValue)}");
|
||||||
|
|
||||||
|
sb.AppendLine(";");
|
||||||
|
sb.AppendLine($"END\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.AppendLine("-- No new fields to add");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashSet<string> ExtractFieldNamesFromCreateScript(string sqlScript)
|
||||||
|
{
|
||||||
|
var fieldNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
// Simple parsing - extract field names between [ and ]
|
||||||
|
var lines = sqlScript.Split('\n');
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
var trimmedLine = line.Trim();
|
||||||
|
if (trimmedLine.StartsWith("[") && trimmedLine.Contains("]"))
|
||||||
|
{
|
||||||
|
var fieldName = trimmedLine.Substring(1, trimmedLine.IndexOf("]") - 1);
|
||||||
|
if (fieldName != "Id" &&
|
||||||
|
fieldName != "TenantId" &&
|
||||||
|
fieldName != "CreationTime" &&
|
||||||
|
fieldName != "CreatorId" &&
|
||||||
|
fieldName != "LastModificationTime" &&
|
||||||
|
fieldName != "LastModifierId" &&
|
||||||
|
fieldName != "IsDeleted" &&
|
||||||
|
fieldName != "DeleterId" &&
|
||||||
|
fieldName != "DeletionTime")
|
||||||
|
{
|
||||||
|
fieldNames.Add(fieldName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldNames;
|
||||||
|
}
|
||||||
|
|
||||||
private string GetSqlType(string fieldType, int? maxLength)
|
private string GetSqlType(string fieldType, int? maxLength)
|
||||||
{
|
{
|
||||||
return fieldType.ToLower() switch
|
return fieldType.ToLower() switch
|
||||||
|
|
|
||||||
|
|
@ -105,13 +105,26 @@ public class CustomEntityAppService : CrudAppService<
|
||||||
if (entity == null)
|
if (entity == null)
|
||||||
throw new EntityNotFoundException(typeof(CustomEntity), id);
|
throw new EntityNotFoundException(typeof(CustomEntity), id);
|
||||||
|
|
||||||
|
// If entity structure has changed and migration was applied, reset migration status
|
||||||
|
bool structureChanged = entity.Name != input.Name ||
|
||||||
|
entity.TableName != input.TableName ||
|
||||||
|
entity.IsFullAuditedEntity != input.IsFullAuditedEntity ||
|
||||||
|
entity.IsMultiTenant != input.IsMultiTenant ||
|
||||||
|
FieldsHaveChanged(entity.Fields, input.Fields);
|
||||||
|
|
||||||
|
if (structureChanged && entity.MigrationStatus == "applied")
|
||||||
|
{
|
||||||
|
entity.MigrationStatus = "pending";
|
||||||
|
entity.MigrationId = null;
|
||||||
|
}
|
||||||
|
|
||||||
entity.Name = input.Name;
|
entity.Name = input.Name;
|
||||||
entity.DisplayName = input.DisplayName;
|
entity.DisplayName = input.DisplayName;
|
||||||
entity.TableName = input.TableName;
|
entity.TableName = input.TableName;
|
||||||
entity.Description = input.Description;
|
entity.Description = input.Description;
|
||||||
entity.IsActive = input.IsActive;
|
entity.IsActive = input.IsActive;
|
||||||
entity.HasAuditFields = input.HasAuditFields;
|
entity.IsFullAuditedEntity = input.IsFullAuditedEntity;
|
||||||
entity.HasSoftDelete = input.HasSoftDelete;
|
entity.IsMultiTenant = input.IsMultiTenant;
|
||||||
|
|
||||||
var updatedFields = new List<CustomEntityField>();
|
var updatedFields = new List<CustomEntityField>();
|
||||||
|
|
||||||
|
|
@ -183,8 +196,8 @@ public class CustomEntityAppService : CrudAppService<
|
||||||
TableName = input.TableName,
|
TableName = input.TableName,
|
||||||
Description = input.Description,
|
Description = input.Description,
|
||||||
IsActive = input.IsActive,
|
IsActive = input.IsActive,
|
||||||
HasAuditFields = input.HasAuditFields,
|
IsFullAuditedEntity = input.IsFullAuditedEntity,
|
||||||
HasSoftDelete = input.HasSoftDelete,
|
IsMultiTenant = input.IsMultiTenant,
|
||||||
MigrationStatus = "pending"
|
MigrationStatus = "pending"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -212,6 +225,32 @@ public class CustomEntityAppService : CrudAppService<
|
||||||
return ObjectMapper.Map<CustomEntity, CustomEntityDto>(entity);
|
return ObjectMapper.Map<CustomEntity, CustomEntityDto>(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool FieldsHaveChanged(ICollection<CustomEntityField> existingFields, List<CreateUpdateCustomEntityFieldDto> inputFields)
|
||||||
|
{
|
||||||
|
if (existingFields.Count != inputFields.Count)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var existingFieldsList = existingFields.OrderBy(f => f.DisplayOrder).ToList();
|
||||||
|
var inputFieldsList = inputFields.OrderBy(f => f.DisplayOrder).ToList();
|
||||||
|
|
||||||
|
for (int i = 0; i < existingFieldsList.Count; i++)
|
||||||
|
{
|
||||||
|
var existing = existingFieldsList[i];
|
||||||
|
var input = inputFieldsList[i];
|
||||||
|
|
||||||
|
if (existing.Name != input.Name ||
|
||||||
|
existing.Type != input.Type ||
|
||||||
|
existing.IsRequired != input.IsRequired ||
|
||||||
|
existing.MaxLength != input.MaxLength ||
|
||||||
|
existing.IsUnique != input.IsUnique)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task DeleteAsync(Guid id)
|
public override async Task DeleteAsync(Guid id)
|
||||||
{
|
{
|
||||||
// İlgili entity'nin var olup olmadığını kontrol et
|
// İlgili entity'nin var olup olmadığını kontrol et
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ public class CustomEntity : FullAuditedEntity<Guid>, IMultiTenant
|
||||||
public string TableName { get; set; } = string.Empty;
|
public string TableName { get; set; } = string.Empty;
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
public bool HasAuditFields { get; set; } = true;
|
public bool IsFullAuditedEntity { get; set; } = true;
|
||||||
public bool HasSoftDelete { get; set; } = true;
|
public bool IsMultiTenant { get; set; } = false;
|
||||||
public string MigrationStatus { get; set; } = "pending";
|
public string MigrationStatus { get; set; } = "pending";
|
||||||
public Guid? MigrationId { get; set; }
|
public Guid? MigrationId { get; set; }
|
||||||
public string EndpointStatus { get; set; } = "pending"; // "pending" | "applied" | "failed"
|
public string EndpointStatus { get; set; } = "pending"; // "pending" | "applied" | "failed"
|
||||||
|
|
|
||||||
|
|
@ -54,10 +54,16 @@ public class DynamicEntityManager : IDynamicEntityManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity.HasAuditFields)
|
if (entity.IsMultiTenant && data.TryGetProperty("TenantId", out var tenantIdValue))
|
||||||
{
|
{
|
||||||
columns.AddRange(new[] { "[CreationTime]", "[LastModificationTime]" });
|
columns.Add("[TenantId]");
|
||||||
values.AddRange(new[] { "SYSUTCDATETIME()", "SYSUTCDATETIME()" });
|
values.Add($"'{tenantIdValue.GetGuid()}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity.IsFullAuditedEntity)
|
||||||
|
{
|
||||||
|
columns.AddRange(new[] { "[CreationTime]", "[LastModificationTime]", "[IsDeleted]" });
|
||||||
|
values.AddRange(new[] { "SYSUTCDATETIME()", "NULL", "0" });
|
||||||
}
|
}
|
||||||
|
|
||||||
var insertQuery = $"INSERT INTO [{tableName}] ({string.Join(", ", columns)}) VALUES ({string.Join(", ", values)})";
|
var insertQuery = $"INSERT INTO [{tableName}] ({string.Join(", ", columns)}) VALUES ({string.Join(", ", values)})";
|
||||||
|
|
@ -86,7 +92,7 @@ public class DynamicEntityManager : IDynamicEntityManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity.HasAuditFields)
|
if (entity.IsFullAuditedEntity)
|
||||||
setParts.Add("[LastModificationTime] = SYSUTCDATETIME()");
|
setParts.Add("[LastModificationTime] = SYSUTCDATETIME()");
|
||||||
|
|
||||||
var updateQuery = $"UPDATE [{tableName}] SET {string.Join(", ", setParts)} WHERE Id = '{id}'";
|
var updateQuery = $"UPDATE [{tableName}] SET {string.Join(", ", setParts)} WHERE Id = '{id}'";
|
||||||
|
|
@ -107,12 +113,10 @@ public class DynamicEntityManager : IDynamicEntityManager
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
string deleteQuery;
|
string deleteQuery;
|
||||||
if (entity.HasSoftDelete)
|
if (entity.IsFullAuditedEntity)
|
||||||
{
|
{
|
||||||
var parts = new List<string> { "[IsDeleted] = 1" };
|
// Full audited entities always have soft delete
|
||||||
if (entity.HasAuditFields)
|
deleteQuery = $"UPDATE [{tableName}] SET [IsDeleted] = 1, [DeletionTime] = SYSUTCDATETIME() WHERE Id = '{id}'";
|
||||||
parts.Add("[DeletionTime] = SYSUTCDATETIME()");
|
|
||||||
deleteQuery = $"UPDATE [{tableName}] SET {string.Join(", ", parts)} WHERE Id = '{id}'";
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency
|
||||||
{
|
{
|
||||||
transaction = await con.BeginTransactionAsync();
|
transaction = await con.BeginTransactionAsync();
|
||||||
unitOfWorkManager.Current.AddTransactionApi(key, new DapperTransactionApi(transaction, cancellationTokenProvider));
|
unitOfWorkManager.Current.AddTransactionApi(key, new DapperTransactionApi(transaction, cancellationTokenProvider));
|
||||||
transactions.Add(key, transaction);
|
transactions[key] = transaction;
|
||||||
unitOfWorkManager.Current.OnCompleted(() =>
|
unitOfWorkManager.Current.OnCompleted(() =>
|
||||||
{
|
{
|
||||||
transaction = null;
|
transaction = null;
|
||||||
|
|
@ -56,7 +56,7 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency
|
||||||
if (connection == null)
|
if (connection == null)
|
||||||
{
|
{
|
||||||
connection = new SqlConnection(cs);
|
connection = new SqlConnection(cs);
|
||||||
connections.Add(key, connection);
|
connections[key] = connection; // Use indexer instead of Add to avoid duplicate key exception
|
||||||
}
|
}
|
||||||
if (connection.State != ConnectionState.Open)
|
if (connection.State != ConnectionState.Open)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
||||||
namespace Kurs.Platform.Migrations
|
namespace Kurs.Platform.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PlatformDbContext))]
|
[DbContext(typeof(PlatformDbContext))]
|
||||||
[Migration("20251105111749_Initial")]
|
[Migration("20251105142841_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -2928,12 +2928,6 @@ namespace Kurs.Platform.Migrations
|
||||||
.HasMaxLength(50)
|
.HasMaxLength(50)
|
||||||
.HasColumnType("nvarchar(50)");
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
b.Property<bool>("HasAuditFields")
|
|
||||||
.HasColumnType("bit");
|
|
||||||
|
|
||||||
b.Property<bool>("HasSoftDelete")
|
|
||||||
.HasColumnType("bit");
|
|
||||||
|
|
||||||
b.Property<bool>("IsActive")
|
b.Property<bool>("IsActive")
|
||||||
.HasColumnType("bit");
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
|
@ -2943,6 +2937,12 @@ namespace Kurs.Platform.Migrations
|
||||||
.HasDefaultValue(false)
|
.HasDefaultValue(false)
|
||||||
.HasColumnName("IsDeleted");
|
.HasColumnName("IsDeleted");
|
||||||
|
|
||||||
|
b.Property<bool>("IsFullAuditedEntity")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<bool>("IsMultiTenant")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
b.Property<DateTime?>("LastModificationTime")
|
b.Property<DateTime?>("LastModificationTime")
|
||||||
.HasColumnType("datetime2")
|
.HasColumnType("datetime2")
|
||||||
.HasColumnName("LastModificationTime");
|
.HasColumnName("LastModificationTime");
|
||||||
|
|
@ -2137,8 +2137,8 @@ namespace Kurs.Platform.Migrations
|
||||||
TableName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
TableName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||||
Description = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
Description = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||||
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||||
HasAuditFields = table.Column<bool>(type: "bit", nullable: false),
|
IsFullAuditedEntity = table.Column<bool>(type: "bit", nullable: false),
|
||||||
HasSoftDelete = table.Column<bool>(type: "bit", nullable: false),
|
IsMultiTenant = table.Column<bool>(type: "bit", nullable: false),
|
||||||
MigrationStatus = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
MigrationStatus = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
MigrationId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
MigrationId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
EndpointStatus = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
EndpointStatus = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
|
|
@ -2925,12 +2925,6 @@ namespace Kurs.Platform.Migrations
|
||||||
.HasMaxLength(50)
|
.HasMaxLength(50)
|
||||||
.HasColumnType("nvarchar(50)");
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
b.Property<bool>("HasAuditFields")
|
|
||||||
.HasColumnType("bit");
|
|
||||||
|
|
||||||
b.Property<bool>("HasSoftDelete")
|
|
||||||
.HasColumnType("bit");
|
|
||||||
|
|
||||||
b.Property<bool>("IsActive")
|
b.Property<bool>("IsActive")
|
||||||
.HasColumnType("bit");
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
|
@ -2940,6 +2934,12 @@ namespace Kurs.Platform.Migrations
|
||||||
.HasDefaultValue(false)
|
.HasDefaultValue(false)
|
||||||
.HasColumnName("IsDeleted");
|
.HasColumnName("IsDeleted");
|
||||||
|
|
||||||
|
b.Property<bool>("IsFullAuditedEntity")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<bool>("IsMultiTenant")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
b.Property<DateTime?>("LastModificationTime")
|
b.Property<DateTime?>("LastModificationTime")
|
||||||
.HasColumnType("datetime2")
|
.HasColumnType("datetime2")
|
||||||
.HasColumnName("LastModificationTime");
|
.HasColumnName("LastModificationTime");
|
||||||
|
|
|
||||||
|
|
@ -485,14 +485,14 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
|
|
||||||
{/* Endpoints List */}
|
{/* Endpoints List */}
|
||||||
{filteredEndpoints.length > 0 ? (
|
{filteredEndpoints.length > 0 ? (
|
||||||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 xl:grid-cols-5 gap-6">
|
||||||
{filteredEndpoints.map((endpoint) => (
|
{filteredEndpoints.map((endpoint) => (
|
||||||
<div
|
<div
|
||||||
key={endpoint.id}
|
key={endpoint.id}
|
||||||
className="bg-white rounded-lg border border-slate-200 shadow-sm"
|
className="bg-white rounded-lg border border-slate-200 shadow-sm"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="p-6 cursor-pointer hover:bg-slate-50 transition-colors"
|
className="p-4 cursor-pointer hover:bg-slate-50 transition-colors"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const newSelectedEndpoint = selectedEndpoint === endpoint.id ? null : endpoint.id
|
const newSelectedEndpoint = selectedEndpoint === endpoint.id ? null : endpoint.id
|
||||||
setSelectedEndpoint(newSelectedEndpoint)
|
setSelectedEndpoint(newSelectedEndpoint)
|
||||||
|
|
@ -505,7 +505,7 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
{/* Sol taraf */}
|
{/* Sol taraf */}
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<span
|
<span
|
||||||
className={`px-3 py-1 text-sm font-medium rounded-full border ${getMethodColor(
|
className={`px-4 py-1 text-sm font-medium rounded-full border ${getMethodColor(
|
||||||
endpoint.method,
|
endpoint.method,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
|
|
@ -513,37 +513,33 @@ const CrudEndpointManager: React.FC = () => {
|
||||||
</span>
|
</span>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold text-slate-900">{endpoint.name}</h3>
|
<h3 className="text-lg font-semibold text-slate-900">{endpoint.name}</h3>
|
||||||
<code className="text-sm bg-slate-100 text-slate-700 px-2 py-1 rounded">
|
|
||||||
{endpoint.path}
|
|
||||||
</code>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sağ taraf */}
|
{/* Sağ taraf */}
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{endpoint.type === 'generated' && (
|
|
||||||
<span className="bg-emerald-100 text-emerald-700 text-xs px-2 py-1 rounded-full">
|
|
||||||
{translate('::App.DeveloperKit.Endpoint.AutoGenerated')}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<FaCheckCircle className="w-5 h-5 text-green-500" />
|
<FaCheckCircle className="w-3 h-3 text-green-500" />
|
||||||
<span className="text-sm text-slate-500">
|
<span className="text-sm text-slate-500">
|
||||||
{translate('::App.DeveloperKit.Endpoint.Active')}
|
{translate('::App.DeveloperKit.Endpoint.Active')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{endpoint.description && (
|
<div className="mt-2">
|
||||||
<p className="text-slate-600 text-sm mt-2">{endpoint.description}</p>
|
<code className="text-sm bg-slate-100 text-slate-700 px-2 py-1 rounded">
|
||||||
)}
|
{endpoint.path}
|
||||||
|
</code>
|
||||||
|
{endpoint.description && (
|
||||||
|
<p className="text-slate-600 text-sm mt-2">{endpoint.description}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Expanded Details */}
|
{/* Expanded Details */}
|
||||||
{selectedEndpoint === endpoint.id && (
|
{selectedEndpoint === endpoint.id && (
|
||||||
<div className="border-t border-slate-200 p-6 bg-slate-50">
|
<div className="border-t border-slate-200 p-4 bg-slate-50">
|
||||||
<div className="grid grid-cols-1 gap-6">
|
<div className="grid grid-cols-1 gap-6">
|
||||||
{/* Request Details */}
|
{/* Request Details */}
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,8 @@ const validationSchema = Yup.object({
|
||||||
)
|
)
|
||||||
.min(1, 'At least one field is required'),
|
.min(1, 'At least one field is required'),
|
||||||
isActive: Yup.boolean(),
|
isActive: Yup.boolean(),
|
||||||
hasAuditFields: Yup.boolean(),
|
isFullAuditedEntity: Yup.boolean(),
|
||||||
hasSoftDelete: Yup.boolean(),
|
isMultiTenant: Yup.boolean(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const EntityEditor: React.FC = () => {
|
const EntityEditor: React.FC = () => {
|
||||||
|
|
@ -72,15 +72,10 @@ const EntityEditor: React.FC = () => {
|
||||||
},
|
},
|
||||||
] as CustomEntityField[],
|
] as CustomEntityField[],
|
||||||
isActive: true,
|
isActive: true,
|
||||||
hasAuditFields: true,
|
isFullAuditedEntity: true,
|
||||||
hasSoftDelete: true,
|
isMultiTenant: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Check if migration is applied to disable certain fields
|
|
||||||
const isMigrationApplied = Boolean(
|
|
||||||
isEditing && id && getEntity(id)?.migrationStatus === 'applied',
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isEditing && id) {
|
if (isEditing && id) {
|
||||||
const entity = getEntity(id)
|
const entity = getEntity(id)
|
||||||
|
|
@ -98,8 +93,8 @@ const EntityEditor: React.FC = () => {
|
||||||
description: entity.description || '',
|
description: entity.description || '',
|
||||||
fields: sortedFields,
|
fields: sortedFields,
|
||||||
isActive: entity.isActive,
|
isActive: entity.isActive,
|
||||||
hasAuditFields: entity.hasAuditFields,
|
isFullAuditedEntity: entity.isFullAuditedEntity,
|
||||||
hasSoftDelete: entity.hasSoftDelete,
|
isMultiTenant: entity.isMultiTenant,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -132,8 +127,8 @@ const EntityEditor: React.FC = () => {
|
||||||
description: values.description.trim(),
|
description: values.description.trim(),
|
||||||
fields: sanitizedFields,
|
fields: sanitizedFields,
|
||||||
isActive: values.isActive,
|
isActive: values.isActive,
|
||||||
hasAuditFields: values.hasAuditFields,
|
isFullAuditedEntity: values.isFullAuditedEntity,
|
||||||
hasSoftDelete: values.hasSoftDelete,
|
isMultiTenant: values.isMultiTenant,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEditing && id) {
|
if (isEditing && id) {
|
||||||
|
|
@ -208,7 +203,17 @@ const EntityEditor: React.FC = () => {
|
||||||
type="button"
|
type="button"
|
||||||
onClick={submitForm}
|
onClick={submitForm}
|
||||||
disabled={isSubmitting || !isValid}
|
disabled={isSubmitting || !isValid}
|
||||||
className="flex items-center gap-1 bg-gradient-to-r from-green-600 to-green-700 hover:from-green-700 hover:to-green-800 text-white font-semibold px-2 py-1.5 rounded shadow transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed text-sm"
|
className={`
|
||||||
|
flex items-center gap-1
|
||||||
|
px-2 py-1.5 rounded text-sm transition-all duration-200
|
||||||
|
text-white
|
||||||
|
bg-gradient-to-r from-green-600 to-green-700
|
||||||
|
hover:from-green-700 hover:to-green-800
|
||||||
|
disabled:from-gray-400 disabled:to-gray-500
|
||||||
|
disabled:text-gray-200
|
||||||
|
disabled:cursor-not-allowed
|
||||||
|
disabled:hover:from-gray-400 disabled:hover:to-gray-500
|
||||||
|
`}
|
||||||
>
|
>
|
||||||
<FaSave className="w-3 h-3" />
|
<FaSave className="w-3 h-3" />
|
||||||
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
|
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
|
||||||
|
|
@ -219,6 +224,25 @@ const EntityEditor: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Form className="grid grid-cols-1 lg:grid-cols-4 gap-4 pt-2">
|
<Form className="grid grid-cols-1 lg:grid-cols-4 gap-4 pt-2">
|
||||||
|
{/* Migration Status Info Banner */}
|
||||||
|
{isEditing && id && getEntity(id)?.migrationStatus === 'applied' && (
|
||||||
|
<div className="col-span-4 bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="bg-yellow-100 p-2 rounded-lg">
|
||||||
|
<FaDatabase className="w-5 h-5 text-yellow-600" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="text-sm font-semibold text-yellow-900 mb-1">
|
||||||
|
Migration Applied - Changes Will Require New Migration
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-yellow-700">
|
||||||
|
This entity has been migrated to the database. Any structural changes you make will reset the migration status to "pending", and you'll need to generate and apply a new migration to update the database schema.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Basic Entity Information */}
|
{/* Basic Entity Information */}
|
||||||
<div className="space-y-4 col-span-1">
|
<div className="space-y-4 col-span-1">
|
||||||
<div className="bg-white rounded-lg shadow-sm border border-slate-200 p-3">
|
<div className="bg-white rounded-lg shadow-sm border border-slate-200 p-3">
|
||||||
|
|
@ -248,7 +272,6 @@ const EntityEditor: React.FC = () => {
|
||||||
setFieldValue('displayName', values.name)
|
setFieldValue('displayName', values.name)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={isMigrationApplied}
|
|
||||||
placeholder="e.g., Product, User, Order"
|
placeholder="e.g., Product, User, Order"
|
||||||
className="px-2 py-1.5 bg-slate-50 focus:bg-white transition-all duration-200 text-sm h-7"
|
className="px-2 py-1.5 bg-slate-50 focus:bg-white transition-all duration-200 text-sm h-7"
|
||||||
/>
|
/>
|
||||||
|
|
@ -276,7 +299,6 @@ const EntityEditor: React.FC = () => {
|
||||||
<Field
|
<Field
|
||||||
name="tableName"
|
name="tableName"
|
||||||
component={Input}
|
component={Input}
|
||||||
disabled={isMigrationApplied}
|
|
||||||
placeholder="e.g., Products, Users, Orders"
|
placeholder="e.g., Products, Users, Orders"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
@ -290,6 +312,8 @@ const EntityEditor: React.FC = () => {
|
||||||
name="description"
|
name="description"
|
||||||
component={Input}
|
component={Input}
|
||||||
placeholder="Brief description of this entity"
|
placeholder="Brief description of this entity"
|
||||||
|
textArea={true}
|
||||||
|
rows={3}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
|
@ -297,12 +321,18 @@ const EntityEditor: React.FC = () => {
|
||||||
<Field name="isActive" component={Checkbox} />
|
<Field name="isActive" component={Checkbox} />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
<FormItem label={translate('::App.DeveloperKit.EntityEditor.Audit')}>
|
<FormItem label="Full Audited Entity">
|
||||||
<Field name="hasAuditFields" component={Checkbox} />
|
<Field name="isFullAuditedEntity" component={Checkbox} />
|
||||||
|
<p className="text-xs text-slate-500 mt-1">
|
||||||
|
Includes CreationTime, CreatorId, LastModificationTime, LastModifierId, IsDeleted, DeleterId, DeletionTime
|
||||||
|
</p>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
<FormItem label={translate('::App.DeveloperKit.EntityEditor.SoftDelete')}>
|
<FormItem label="Multi-Tenant">
|
||||||
<Field name="hasSoftDelete" component={Checkbox} />
|
<Field name="isMultiTenant" component={Checkbox} />
|
||||||
|
<p className="text-xs text-slate-500 mt-1">
|
||||||
|
Adds TenantId column for multi-tenancy support
|
||||||
|
</p>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -334,19 +364,28 @@ const EntityEditor: React.FC = () => {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
isRequired: false,
|
isRequired: false,
|
||||||
description: '',
|
description: '',
|
||||||
// Assign next sequential displayOrder
|
|
||||||
displayOrder: (values.fields?.length ?? 0) + 1,
|
displayOrder: (values.fields?.length ?? 0) + 1,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="flex items-center gap-1 bg-gradient-to-r from-blue-600 to-blue-700 text-white px-2 py-1.5 rounded hover:from-blue-700 hover:to-blue-800 transition-all duration-200 text-sm"
|
className={`
|
||||||
>
|
flex items-center gap-1
|
||||||
|
px-2 py-1.5 rounded text-sm transition-all duration-200
|
||||||
|
text-white
|
||||||
|
bg-gradient-to-r from-blue-600 to-blue-700
|
||||||
|
hover:from-blue-700 hover:to-blue-800
|
||||||
|
disabled:from-gray-400 disabled:to-gray-500
|
||||||
|
disabled:text-gray-200
|
||||||
|
disabled:cursor-not-allowed
|
||||||
|
disabled:hover:from-gray-400 disabled:hover:to-gray-500
|
||||||
|
`}
|
||||||
|
>
|
||||||
<FaPlus className="w-2.5 h-2.5" />
|
<FaPlus className="w-2.5 h-2.5" />
|
||||||
{translate('::App.DeveloperKit.EntityEditor.AddField')}
|
{translate('::App.DeveloperKit.EntityEditor.AddField')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-12 gap-2 mb-2">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-12 gap-2 mb-2">
|
||||||
<div className="col-span-1">Order *</div>
|
<div className="col-span-1 font-bold">Order *</div>
|
||||||
|
|
||||||
<div className="col-span-2 font-bold">
|
<div className="col-span-2 font-bold">
|
||||||
{translate('::App.DeveloperKit.EntityEditor.FieldName')} *
|
{translate('::App.DeveloperKit.EntityEditor.FieldName')} *
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,16 @@ import {
|
||||||
FaCheckCircle,
|
FaCheckCircle,
|
||||||
FaTable,
|
FaTable,
|
||||||
FaBolt,
|
FaBolt,
|
||||||
|
FaSync,
|
||||||
} from 'react-icons/fa'
|
} from 'react-icons/fa'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
||||||
const EntityManager: React.FC = () => {
|
const EntityManager: React.FC = () => {
|
||||||
const { entities, deleteEntity, toggleEntityActiveStatus, refreshEntities } = useEntities()
|
const { entities, deleteEntity, toggleEntityActiveStatus, refreshEntities, generateMigration } = useEntities()
|
||||||
const [searchTerm, setSearchTerm] = useState('')
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [filterActive, setFilterActive] = useState<'all' | 'active' | 'inactive'>('all')
|
const [filterActive, setFilterActive] = useState<'all' | 'active' | 'inactive'>('all')
|
||||||
|
const [generatingMigration, setGeneratingMigration] = useState<string | null>(null)
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
// Sayfa odaklandığında varlıkları yenile
|
// Sayfa odaklandığında varlıkları yenile
|
||||||
|
|
@ -77,6 +79,19 @@ const EntityManager: React.FC = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleGenerateMigration = async (entityId: string) => {
|
||||||
|
try {
|
||||||
|
setGeneratingMigration(entityId)
|
||||||
|
await generateMigration(entityId)
|
||||||
|
alert('Migration generated successfully!')
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to generate migration:', err)
|
||||||
|
alert('Failed to generate migration. Please try again.')
|
||||||
|
} finally {
|
||||||
|
setGeneratingMigration(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const stats = {
|
const stats = {
|
||||||
total: entities.length,
|
total: entities.length,
|
||||||
active: entities.filter((e) => e.isActive).length,
|
active: entities.filter((e) => e.isActive).length,
|
||||||
|
|
@ -287,17 +302,39 @@ const EntityManager: React.FC = () => {
|
||||||
<span className="text-sm font-medium text-slate-700">
|
<span className="text-sm font-medium text-slate-700">
|
||||||
{translate('::App.DeveloperKit.Entity.MigrationStatus')}
|
{translate('::App.DeveloperKit.Entity.MigrationStatus')}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<div className="flex items-center gap-2">
|
||||||
className={`text-xs px-2 py-1 rounded-full ${
|
<span
|
||||||
entity.migrationStatus === 'applied'
|
className={`text-xs px-2 py-1 rounded-full ${
|
||||||
? 'bg-green-100 text-green-700'
|
entity.migrationStatus === 'applied'
|
||||||
: entity.migrationStatus === 'pending'
|
? 'bg-green-100 text-green-700'
|
||||||
? 'bg-yellow-100 text-yellow-700'
|
: entity.migrationStatus === 'pending'
|
||||||
: 'bg-red-100 text-red-700'
|
? 'bg-yellow-100 text-yellow-700'
|
||||||
}`}
|
: 'bg-red-100 text-red-700'
|
||||||
>
|
}`}
|
||||||
{entity.migrationStatus}
|
>
|
||||||
</span>
|
{entity.migrationStatus}
|
||||||
|
</span>
|
||||||
|
{entity.migrationStatus === 'pending' && (
|
||||||
|
<button
|
||||||
|
onClick={() => handleGenerateMigration(entity.id)}
|
||||||
|
disabled={generatingMigration === entity.id}
|
||||||
|
className="flex items-center gap-1 px-2 py-1 text-xs bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
title="Generate Migration"
|
||||||
|
>
|
||||||
|
{generatingMigration === entity.id ? (
|
||||||
|
<>
|
||||||
|
<FaSync className="w-3 h-3 animate-spin" />
|
||||||
|
<span>Generating...</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<FaBolt className="w-3 h-3" />
|
||||||
|
<span>Generate</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-sm font-medium text-slate-700">
|
<span className="text-sm font-medium text-slate-700">
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ export interface CustomEntity {
|
||||||
tableName: string;
|
tableName: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
hasAuditFields: boolean;
|
isFullAuditedEntity: boolean;
|
||||||
hasSoftDelete: boolean;
|
isMultiTenant: boolean;
|
||||||
migrationStatus: "pending" | "applied" | "failed";
|
migrationStatus: "pending" | "applied" | "failed";
|
||||||
endpointStatus: "pending" | "applied" | "failed";
|
endpointStatus: "pending" | "applied" | "failed";
|
||||||
migrationId?: string;
|
migrationId?: string;
|
||||||
|
|
@ -48,8 +48,8 @@ export interface CreateUpdateCustomEntityDto {
|
||||||
tableName: string;
|
tableName: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
hasAuditFields: boolean;
|
isFullAuditedEntity: boolean;
|
||||||
hasSoftDelete: boolean;
|
isMultiTenant: boolean;
|
||||||
fields: CreateUpdateCustomEntityFieldDto[];
|
fields: CreateUpdateCustomEntityFieldDto[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue