improvement: Tag 테이블 신설 및 도메인 모델 확정 (#243) #244

Merged
seonkyu.kim merged 1 commits from improvement/#243-tag-model into develop 2026-02-25 08:42:00 +00:00
11 changed files with 1402 additions and 0 deletions
Showing only changes of commit c458cfe4e7 - Show all commits

View File

@ -59,4 +59,10 @@ public static class ErrorCodes
public const string FileNotFound = "181"; public const string FileNotFound = "181";
public const string FileTypeNotAllowed = "182"; public const string FileTypeNotAllowed = "182";
public const string FileSizeExceeded = "183"; public const string FileSizeExceeded = "183";
// === Tag (9) ===
public const string TagNotFound = "191";
public const string TagNameDuplicate = "192";
public const string TagNameImmutable = "193";
public const string TagLimitExceeded = "194";
} }

View File

@ -35,4 +35,5 @@ public class Service : BaseEntity
public ICollection<ServiceIp> ServiceIps { get; set; } = new List<ServiceIp>(); public ICollection<ServiceIp> ServiceIps { get; set; } = new List<ServiceIp>();
public ICollection<Device> Devices { get; set; } = new List<Device>(); public ICollection<Device> Devices { get; set; } = new List<Device>();
public ICollection<Message> Messages { get; set; } = new List<Message>(); public ICollection<Message> Messages { get; set; } = new List<Message>();
public ICollection<Tag> TagList { get; set; } = new List<Tag>();
} }

View File

@ -0,0 +1,15 @@
namespace SPMS.Domain.Entities;
public class Tag : BaseEntity
{
public long ServiceId { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public DateTime CreatedAt { get; set; }
public long CreatedBy { get; set; }
public DateTime? UpdatedAt { get; set; }
// Navigation
public Service Service { get; set; } = null!;
public Admin CreatedByAdmin { get; set; } = null!;
}

View File

@ -0,0 +1,9 @@
using SPMS.Domain.Entities;
namespace SPMS.Domain.Interfaces;
public interface ITagRepository : IRepository<Tag>
{
Task<Tag?> GetByIdWithServiceAsync(long id);
Task<bool> ExistsInServiceAsync(long serviceId, string name);
}

View File

@ -25,6 +25,7 @@ public class AppDbContext : DbContext
public DbSet<Banner> Banners => Set<Banner>(); public DbSet<Banner> Banners => Set<Banner>();
public DbSet<Faq> Faqs => Set<Faq>(); public DbSet<Faq> Faqs => Set<Faq>();
public DbSet<AppConfig> AppConfigs => Set<AppConfig>(); public DbSet<AppConfig> AppConfigs => Set<AppConfig>();
public DbSet<Tag> Tags => Set<Tag>();
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {

View File

@ -43,6 +43,7 @@ public static class DependencyInjection
services.AddScoped<IPushSendLogRepository, PushSendLogRepository>(); services.AddScoped<IPushSendLogRepository, PushSendLogRepository>();
services.AddScoped<IDailyStatRepository, DailyStatRepository>(); services.AddScoped<IDailyStatRepository, DailyStatRepository>();
services.AddScoped<IWebhookLogRepository, WebhookLogRepository>(); services.AddScoped<IWebhookLogRepository, WebhookLogRepository>();
services.AddScoped<ITagRepository, TagRepository>();
// External Services // External Services
services.AddScoped<IJwtService, JwtService>(); services.AddScoped<IJwtService, JwtService>();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,67 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SPMS.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddTagTable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Tag",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
ServiceId = table.Column<long>(type: "bigint", nullable: false),
Name = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Description = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
CreatedBy = table.Column<long>(type: "bigint", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Tag", x => x.Id);
table.ForeignKey(
name: "FK_Tag_Admin_CreatedBy",
column: x => x.CreatedBy,
principalTable: "Admin",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Tag_Service_ServiceId",
column: x => x.ServiceId,
principalTable: "Service",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_Tag_CreatedBy",
table: "Tag",
column: "CreatedBy");
migrationBuilder.CreateIndex(
name: "IX_Tag_ServiceId_Name",
table: "Tag",
columns: new[] { "ServiceId", "Name" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Tag");
}
}
}

View File

@ -836,6 +836,45 @@ namespace SPMS.Infrastructure.Migrations
b.ToTable("SystemLog", (string)null); b.ToTable("SystemLog", (string)null);
}); });
modelBuilder.Entity("SPMS.Domain.Entities.Tag", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<long>("CreatedBy")
.HasColumnType("bigint");
b.Property<string>("Description")
.HasMaxLength(200)
.HasColumnType("varchar(200)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<long>("ServiceId")
.HasColumnType("bigint");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("CreatedBy");
b.HasIndex("ServiceId", "Name")
.IsUnique();
b.ToTable("Tag", (string)null);
});
modelBuilder.Entity("SPMS.Domain.Entities.WebhookLog", b => modelBuilder.Entity("SPMS.Domain.Entities.WebhookLog", b =>
{ {
b.Property<long>("Id") b.Property<long>("Id")
@ -1102,6 +1141,25 @@ namespace SPMS.Infrastructure.Migrations
b.Navigation("Service"); b.Navigation("Service");
}); });
modelBuilder.Entity("SPMS.Domain.Entities.Tag", b =>
{
b.HasOne("SPMS.Domain.Entities.Admin", "CreatedByAdmin")
.WithMany()
.HasForeignKey("CreatedBy")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("SPMS.Domain.Entities.Service", "Service")
.WithMany("TagList")
.HasForeignKey("ServiceId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("CreatedByAdmin");
b.Navigation("Service");
});
modelBuilder.Entity("SPMS.Domain.Entities.WebhookLog", b => modelBuilder.Entity("SPMS.Domain.Entities.WebhookLog", b =>
{ {
b.HasOne("SPMS.Domain.Entities.Service", "Service") b.HasOne("SPMS.Domain.Entities.Service", "Service")
@ -1120,6 +1178,8 @@ namespace SPMS.Infrastructure.Migrations
b.Navigation("Messages"); b.Navigation("Messages");
b.Navigation("ServiceIps"); b.Navigation("ServiceIps");
b.Navigation("TagList");
}); });
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }

View File

@ -0,0 +1,34 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using SPMS.Domain.Entities;
namespace SPMS.Infrastructure.Persistence.Configurations;
public class TagConfiguration : IEntityTypeConfiguration<Tag>
{
public void Configure(EntityTypeBuilder<Tag> builder)
{
builder.ToTable("Tag");
builder.HasKey(e => e.Id);
builder.Property(e => e.Id).ValueGeneratedOnAdd();
builder.Property(e => e.Name).HasMaxLength(50).IsRequired();
builder.Property(e => e.Description).HasMaxLength(200);
builder.Property(e => e.CreatedAt).IsRequired();
builder.Property(e => e.CreatedBy).IsRequired();
builder.Property(e => e.UpdatedAt);
builder.HasOne(e => e.Service)
.WithMany(s => s.TagList)
.HasForeignKey(e => e.ServiceId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(e => e.CreatedByAdmin)
.WithMany()
.HasForeignKey(e => e.CreatedBy)
.OnDelete(DeleteBehavior.Restrict);
builder.HasIndex(e => new { e.ServiceId, e.Name }).IsUnique();
}
}

View File

@ -0,0 +1,18 @@
using Microsoft.EntityFrameworkCore;
using SPMS.Domain.Entities;
using SPMS.Domain.Interfaces;
namespace SPMS.Infrastructure.Persistence.Repositories;
public class TagRepository : Repository<Tag>, ITagRepository
{
public TagRepository(AppDbContext context) : base(context) { }
public async Task<Tag?> GetByIdWithServiceAsync(long id)
=> await _dbSet
.Include(t => t.Service)
.FirstOrDefaultAsync(t => t.Id == id);
public async Task<bool> ExistsInServiceAsync(long serviceId, string name)
=> await _dbSet.AnyAsync(t => t.ServiceId == serviceId && t.Name == name);
}