Compare commits

..

No commits in common. "5351d7f782132fdfaa0de74afa07f10017c428b4" and "4406a06970bb305d4bb58f11a33caf10d24c3f0a" have entirely different histories.

20 changed files with 3 additions and 789 deletions

View File

@ -1,21 +0,0 @@
using AutoMapper;
using Models.Entities;
using XisongSpaceBooking_BackEnd.Models.DTOs;
namespace XisongSpaceBooking_BackEnd.Configurations
{
/// <summary>
/// AutoMapper 映射配置檔案
/// </summary>
public class AutoMappingProfile : Profile
{
public AutoMappingProfile()
{
// Department Entity 到 DTO 的映射
CreateMap<DepartmentEntity, DepartmentDto>();
// Role Entity 到 DTO 的映射
CreateMap<RoleEntity, RoleDto>();
}
}
}

View File

@ -1,255 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Models.Entities;
using XisongSpaceBooking_BackEnd.Models.Entities;
namespace XisongSpaceBooking_BackEnd.Configurations
{
/// <summary>
/// 空間預約系統的資料庫內容類別
/// </summary>
public class SpaceBookingDbContext : DbContext
{
public SpaceBookingDbContext(DbContextOptions<SpaceBookingDbContext> options) : base(options)
{
}
/// <summary>
/// 處室資料集
/// </summary>
public DbSet<DepartmentEntity> Departments { get; set; }
/// <summary>
/// 身份資料集
/// </summary>
public DbSet<RoleEntity> Roles { get; set; }
/// <summary>
/// 帳號資料集
/// </summary>
public DbSet<AccountEntity> Accounts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// 配置 Department 實體
ConfigureDepartment(modelBuilder);
// 配置 Role 實體
ConfigureRole(modelBuilder);
// 配置 Account 實體
ConfigureAccount(modelBuilder);
}
/// <summary>
/// 配置 Department 實體
/// </summary>
/// <param name="modelBuilder">模型建構器</param>
private static void ConfigureDepartment(ModelBuilder modelBuilder)
{
var entity = modelBuilder.Entity<DepartmentEntity>();
// 設定資料表名稱
entity.ToTable("departments");
// 設定主鍵
entity.HasKey(d => d.DepartmentId);
entity.Property(d => d.DepartmentId)
.HasColumnName("department_id")
.ValueGeneratedOnAdd()
.HasComment("處室 ID");
// 設定處室名稱
entity.Property(d => d.DepartmentName)
.HasColumnName("department_name")
.HasMaxLength(20)
.IsRequired()
.HasComment("處室名稱");
}
/// <summary>
/// 配置 Role 實體
/// </summary>
/// <param name="modelBuilder">模型建構器</param>
private static void ConfigureRole(ModelBuilder modelBuilder)
{
var entity = modelBuilder.Entity<RoleEntity>();
// 設定資料表名稱
entity.ToTable("roles");
// 設定主鍵
entity.HasKey(r => r.RoleId);
entity.Property(r => r.RoleId)
.HasColumnName("role_id")
.ValueGeneratedOnAdd()
.HasComment("身份 ID");
// 設定身份名稱
entity.Property(r => r.RoleName)
.HasColumnName("role_name")
.HasMaxLength(20)
.IsRequired()
.HasComment("身份名稱");
}
/// <summary>
/// 配置 Account 實體
/// </summary>
/// <param name="modelBuilder">模型建構器</param>
private static void ConfigureAccount(ModelBuilder modelBuilder)
{
var entity = modelBuilder.Entity<AccountEntity>();
// 設定資料表名稱
entity.ToTable("accounts");
// 設定主鍵
entity.HasKey(a => a.AccountId);
entity.Property(a => a.AccountId)
.HasColumnName("account_id")
.ValueGeneratedOnAdd()
.HasComment("帳號 ID");
// 設定使用者姓名
entity.Property(a => a.Name)
.HasColumnName("name")
.HasMaxLength(20)
.IsRequired()
.HasComment("使用者姓名");
// 設定帳號名稱(唯一)
entity.Property(a => a.Username)
.HasColumnName("username")
.HasMaxLength(20)
.IsRequired()
.HasComment("帳號名稱");
// 設定密碼
entity.Property(a => a.Password)
.HasColumnName("password")
.HasMaxLength(255)
.IsRequired()
.HasComment("密碼(加密)");
// 設定電子郵件(唯一)
entity.Property(a => a.Email)
.HasColumnName("email")
.HasMaxLength(50)
.IsRequired()
.HasComment("電子郵件");
// 設定處室 ID外鍵
entity.Property(a => a.DepartmentId)
.HasColumnName("department_id")
.IsRequired()
.HasComment("處室 ID");
// 設定身份 ID外鍵
entity.Property(a => a.RoleId)
.HasColumnName("role_id")
.IsRequired()
.HasComment("身份 ID");
// 設定帳號狀態(轉換為字串儲存)
entity.Property(a => a.Status)
.HasColumnName("status")
.HasConversion(
v => v.ToString().ToLower(),
v => Enum.Parse<AccountStatus>(v, true))
.HasMaxLength(20)
.HasDefaultValue(AccountStatus.Unverified)
.IsRequired()
.HasComment("帳號狀態");
// 設定 BaseEntity 屬性
entity.Property(a => a.CreatedAt)
.HasColumnName("created_at")
.HasDefaultValueSql("CURRENT_TIMESTAMP")
.IsRequired()
.HasComment("建立時間");
entity.Property(a => a.UpdatedAt)
.HasColumnName("updated_at")
.ValueGeneratedOnUpdate()
.HasComment("更新時間");
entity.Property(a => a.ModifiedBy)
.HasColumnName("modified_by")
.IsRequired(false)
.HasComment("最後修改者帳號 ID");
// 設定唯一索引
entity.HasIndex(a => a.Username)
.IsUnique()
.HasDatabaseName("IX_accounts_username");
entity.HasIndex(a => a.Email)
.IsUnique()
.HasDatabaseName("IX_accounts_email");
// 設定外鍵關聯
entity.HasOne(a => a.Department)
.WithMany()
.HasForeignKey(a => a.DepartmentId)
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("FK_accounts_departments");
entity.HasOne(a => a.Role)
.WithMany()
.HasForeignKey(a => a.RoleId)
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("FK_accounts_roles");
// 設定自參考外鍵ModifiedBy
entity.HasOne<AccountEntity>()
.WithMany()
.HasForeignKey(a => a.ModifiedBy)
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_accounts_modified_by");
}
/// <summary>
/// 覆寫 SaveChanges 以自動更新 UpdatedAt 欄位
/// </summary>
public override int SaveChanges()
{
UpdateTimestamps();
return base.SaveChanges();
}
/// <summary>
/// 覆寫 SaveChangesAsync 以自動更新 UpdatedAt 欄位
/// </summary>
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
UpdateTimestamps();
return await base.SaveChangesAsync(cancellationToken);
}
/// <summary>
/// 更新時間戳記
/// </summary>
private void UpdateTimestamps()
{
var entries = ChangeTracker.Entries<BaseEntity>();
foreach (var entry in entries)
{
switch (entry.State)
{
case EntityState.Added:
entry.Entity.UpdatedAt = DateTime.UtcNow;
break;
case EntityState.Modified:
entry.Entity.UpdatedAt = DateTime.UtcNow;
// 確保 CreatedAt 不會被修改
entry.Property(e => e.CreatedAt).IsModified = false;
break;
}
}
}
}
}

View File

@ -1,49 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using XisongSpaceBooking_BackEnd.Models.DTOs;
using XisongSpaceBooking_BackEnd.Services;
namespace XisongSpaceBooking_BackEnd.Controllers
{
/// <summary>
/// 處室管理 API 控制器
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class DepartmentController : ControllerBase
{
private readonly IDepartmentService _departmentService;
/// <summary>
/// 建構函式
/// </summary>
/// <param name="departmentService">處室業務邏輯服務</param>
public DepartmentController(IDepartmentService departmentService)
{
_departmentService = departmentService ?? throw new ArgumentNullException(nameof(departmentService));
}
/// <summary>
/// 取得所有處室資料
/// </summary>
/// <returns>所有處室資料的集合</returns>
/// <response code="200">成功取得處室資料列表</response>
/// <response code="500">伺服器內部錯誤</response>
[HttpGet("GetAll")]
[ProducesResponseType(typeof(IEnumerable<DepartmentDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<IEnumerable<DepartmentDto>>> GetAllAsync()
{
try
{
var departments = await _departmentService.GetAllAsync();
return Ok(departments);
}
catch (Exception ex)
{
// 記錄錯誤(這裡可以加入日誌記錄)
return StatusCode(StatusCodes.Status500InternalServerError,
new { message = "取得處室資料時發生錯誤", error = ex.Message });
}
}
}
}

View File

@ -1,49 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using XisongSpaceBooking_BackEnd.Models.DTOs;
using XisongSpaceBooking_BackEnd.Services;
namespace XisongSpaceBooking_BackEnd.Controllers
{
/// <summary>
/// 身份管理 API 控制器
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class RoleController : ControllerBase
{
private readonly IRoleService _roleService;
/// <summary>
/// 建構函式
/// </summary>
/// <param name="roleService">身份業務邏輯服務</param>
public RoleController(IRoleService roleService)
{
_roleService = roleService ?? throw new ArgumentNullException(nameof(roleService));
}
/// <summary>
/// 取得所有身份資料
/// </summary>
/// <returns>所有身份資料的集合</returns>
/// <response code="200">成功取得身份資料列表</response>
/// <response code="500">伺服器內部錯誤</response>
[HttpGet("GetAll")]
[ProducesResponseType(typeof(IEnumerable<RoleDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<IEnumerable<RoleDto>>> GetAllAsync()
{
try
{
var roles = await _roleService.GetAllAsync();
return Ok(roles);
}
catch (Exception ex)
{
// 記錄錯誤(這裡可以加入日誌記錄)
return StatusCode(StatusCodes.Status500InternalServerError,
new { message = "取得身份資料時發生錯誤", error = ex.Message });
}
}
}
}

View File

@ -1,18 +0,0 @@
namespace XisongSpaceBooking_BackEnd.Models.DTOs
{
/// <summary>
/// 處室資料傳輸物件
/// </summary>
public class DepartmentDto
{
/// <summary>
/// 處室 ID主鍵
/// </summary>
public int DepartmentId { get; set; }
/// <summary>
/// 處室名稱
/// </summary>
public string DepartmentName { get; set; } = string.Empty;
}
}

View File

@ -1,18 +0,0 @@
namespace XisongSpaceBooking_BackEnd.Models.DTOs
{
/// <summary>
/// 身份資料傳輸物件
/// </summary>
public class RoleDto
{
/// <summary>
/// 身份 ID主鍵
/// </summary>
public int RoleId { get; set; }
/// <summary>
/// 身份名稱
/// </summary>
public string RoleName { get; set; } = string.Empty;
}
}

View File

@ -1,81 +0,0 @@
using XisongSpaceBooking_BackEnd.Models.Entities;
namespace Models.Entities
{
/// <summary>
/// 帳號實體類別,對應資料庫 accounts 表格
/// </summary>
public class AccountEntity : BaseEntity
{
/// <summary>
/// 帳號 ID主鍵
/// </summary>
public int AccountId { get; set; }
/// <summary>
/// 使用者姓名
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 帳號名稱(唯一)
/// </summary>
public string Username { get; set; } = string.Empty;
/// <summary>
/// 密碼(加密)
/// </summary>
public string Password { get; set; } = string.Empty;
/// <summary>
/// 電子郵件(唯一)
/// </summary>
public string Email { get; set; } = string.Empty;
/// <summary>
/// 處室 ID外鍵
/// </summary>
public int DepartmentId { get; set; }
/// <summary>
/// 身份 ID外鍵
/// </summary>
public int RoleId { get; set; }
/// <summary>
/// 帳號狀態
/// </summary>
public AccountStatus Status { get; set; }
/// <summary>
/// 關聯的處室資訊
/// </summary>
public DepartmentEntity? Department { get; set; }
/// <summary>
/// 關聯的身份資訊
/// </summary>
public RoleEntity? Role { get; set; }
}
/// <summary>
/// 帳號狀態列舉
/// </summary>
public enum AccountStatus
{
/// <summary>
/// 已啟用
/// </summary>
Enabled,
/// <summary>
/// 已停用
/// </summary>
Disabled,
/// <summary>
/// 未驗證(預設值)
/// </summary>
Unverified
}
}

View File

@ -1,9 +0,0 @@
namespace XisongSpaceBooking_BackEnd.Models.Entities
{
public class BaseEntity
{
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public int? ModifiedBy { get; set; }
}
}

View File

@ -1,20 +0,0 @@
using System.Collections.Generic;
namespace Models.Entities
{
/// <summary>
/// 處室實體類別,對應資料庫 departments 表格
/// </summary>
public class DepartmentEntity
{
/// <summary>
/// 處室 ID主鍵
/// </summary>
public int DepartmentId { get; set; }
/// <summary>
/// 處室名稱
/// </summary>
public string DepartmentName { get; set; }
}
}

View File

@ -1,20 +0,0 @@
using System.Collections.Generic;
namespace Models.Entities
{
/// <summary>
/// 身份實體類別,對應資料庫 roles 表格
/// </summary>
public class RoleEntity
{
/// <summary>
/// 身份 ID主鍵
/// </summary>
public int RoleId { get; set; }
/// <summary>
/// 身份名稱
/// </summary>
public string RoleName { get; set; }
}
}

View File

@ -1,48 +1,11 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;
using XisongSpaceBooking_BackEnd.Configurations;
using XisongSpaceBooking_BackEnd.Repositories;
using XisongSpaceBooking_BackEnd.Services;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// 註冊 AutoMapper 服務
builder.Services.AddAutoMapper(cfg =>
{
}, typeof(AutoMappingProfile).Assembly);
// 註冊 Repository 服務
builder.Services.AddScoped<IRoleRepository, RoleRepository>();
builder.Services.AddScoped<IDepartmentRepository, DepartmentRepository>();
// 註冊 Service 服務
builder.Services.AddScoped<IRoleService, RoleService>();
builder.Services.AddScoped<IDepartmentService, DepartmentService>();
// 配置 Entity Framework 與連線池
builder.Services.AddDbContextPool<SpaceBookingDbContext>(options =>
{
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString),
mySqlOptions =>
{
// 連線池相關設定
mySqlOptions.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null);
});
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "西松高中場地預約/Xisong Space Booking API", Version = "v1" });
});
builder.Services.AddSwaggerGen();
var app = builder.Build();
@ -53,8 +16,6 @@ if (app.Environment.IsDevelopment())
app.UseSwaggerUI();
}
app.UseCors("AllowAll");
app.UseHttpsRedirection();
app.UseAuthorization();

View File

@ -1,34 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Models.Entities;
using XisongSpaceBooking_BackEnd.Configurations;
namespace XisongSpaceBooking_BackEnd.Repositories
{
/// <summary>
/// 處室資料存取實作
/// </summary>
public class DepartmentRepository : IDepartmentRepository
{
private readonly SpaceBookingDbContext _context;
/// <summary>
/// 建構函式
/// </summary>
/// <param name="context">資料庫內容</param>
public DepartmentRepository(SpaceBookingDbContext context)
{
_context = context;
}
/// <summary>
/// 取得所有處室資料
/// </summary>
/// <returns>所有處室資料的集合</returns>
public async Task<IEnumerable<DepartmentEntity>> GetAllAsync()
{
return await _context.Departments
.AsNoTracking()
.ToListAsync();
}
}
}

View File

@ -1,16 +0,0 @@
using Models.Entities;
namespace XisongSpaceBooking_BackEnd.Repositories
{
/// <summary>
/// 處室資料存取介面
/// </summary>
public interface IDepartmentRepository
{
/// <summary>
/// 取得所有處室資料
/// </summary>
/// <returns>所有處室資料的集合</returns>
Task<IEnumerable<DepartmentEntity>> GetAllAsync();
}
}

View File

@ -1,16 +0,0 @@
using Models.Entities;
namespace XisongSpaceBooking_BackEnd.Repositories
{
/// <summary>
/// 身份資料存取介面
/// </summary>
public interface IRoleRepository
{
/// <summary>
/// 取得所有身份資料
/// </summary>
/// <returns>所有身份資料的集合</returns>
Task<IEnumerable<RoleEntity>> GetAllAsync();
}
}

View File

@ -1,34 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Models.Entities;
using XisongSpaceBooking_BackEnd.Configurations;
namespace XisongSpaceBooking_BackEnd.Repositories
{
/// <summary>
/// 身份資料存取實作
/// </summary>
public class RoleRepository : IRoleRepository
{
private readonly SpaceBookingDbContext _context;
/// <summary>
/// 建構函式
/// </summary>
/// <param name="context">資料庫內容</param>
public RoleRepository(SpaceBookingDbContext context)
{
_context = context;
}
/// <summary>
/// 取得所有身份資料
/// </summary>
/// <returns>所有身份資料的集合</returns>
public async Task<IEnumerable<RoleEntity>> GetAllAsync()
{
return await _context.Roles
.AsNoTracking()
.ToListAsync();
}
}
}

View File

@ -1,36 +0,0 @@
using AutoMapper;
using XisongSpaceBooking_BackEnd.Models.DTOs;
using XisongSpaceBooking_BackEnd.Repositories;
namespace XisongSpaceBooking_BackEnd.Services
{
/// <summary>
/// 處室業務邏輯實作
/// </summary>
public class DepartmentService : IDepartmentService
{
private readonly IDepartmentRepository _departmentRepository;
private readonly IMapper _mapper;
/// <summary>
/// 建構函式
/// </summary>
/// <param name="departmentRepository">處室資料存取</param>
/// <param name="mapper">物件映射器</param>
public DepartmentService(IDepartmentRepository departmentRepository, IMapper mapper)
{
_departmentRepository = departmentRepository ?? throw new ArgumentNullException(nameof(departmentRepository));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
/// <summary>
/// 取得所有處室資料
/// </summary>
/// <returns>所有處室 DTO 的集合</returns>
public async Task<IEnumerable<DepartmentDto>> GetAllAsync()
{
var departments = await _departmentRepository.GetAllAsync();
return _mapper.Map<IEnumerable<DepartmentDto>>(departments);
}
}
}

View File

@ -1,16 +0,0 @@
using XisongSpaceBooking_BackEnd.Models.DTOs;
namespace XisongSpaceBooking_BackEnd.Services
{
/// <summary>
/// 處室業務邏輯介面
/// </summary>
public interface IDepartmentService
{
/// <summary>
/// 取得所有處室資料
/// </summary>
/// <returns>所有處室 DTO 的集合</returns>
Task<IEnumerable<DepartmentDto>> GetAllAsync();
}
}

View File

@ -1,16 +0,0 @@
using XisongSpaceBooking_BackEnd.Models.DTOs;
namespace XisongSpaceBooking_BackEnd.Services
{
/// <summary>
/// 身份業務邏輯介面
/// </summary>
public interface IRoleService
{
/// <summary>
/// 取得所有身份資料
/// </summary>
/// <returns>所有身份 DTO 的集合</returns>
Task<IEnumerable<RoleDto>> GetAllAsync();
}
}

View File

@ -1,36 +0,0 @@
using AutoMapper;
using XisongSpaceBooking_BackEnd.Models.DTOs;
using XisongSpaceBooking_BackEnd.Repositories;
namespace XisongSpaceBooking_BackEnd.Services
{
/// <summary>
/// 身份業務邏輯實作
/// </summary>
public class RoleService : IRoleService
{
private readonly IRoleRepository _roleRepository;
private readonly IMapper _mapper;
/// <summary>
/// 建構函式
/// </summary>
/// <param name="roleRepository">身份資料存取</param>
/// <param name="mapper">物件映射器</param>
public RoleService(IRoleRepository roleRepository, IMapper mapper)
{
_roleRepository = roleRepository ?? throw new ArgumentNullException(nameof(roleRepository));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
/// <summary>
/// 取得所有身份資料
/// </summary>
/// <returns>所有身份 DTO 的集合</returns>
public async Task<IEnumerable<RoleDto>> GetAllAsync()
{
var roles = await _roleRepository.GetAllAsync();
return _mapper.Map<IEnumerable<RoleDto>>(roles);
}
}
}

View File

@ -5,8 +5,5 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=xisong_space_booking;User=root;Password=00000000;CharSet=utf8mb4;AllowUserVariables=true;UseAffectedRows=false;"
}
"AllowedHosts": "*"
}