using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using bitforum.DTOs.Response; using bitforum.Repository; using bitforum.Constants; namespace bitforum.Controllers.API { [ApiController] [Route("api/[controller]")] public class SystemController : ControllerBase { private readonly ILogger _logger; private readonly IRedisRepository _redisRepository; private readonly IConfigRepository _configRepository; private readonly DefaultDbContext _db; private ResultDto _result = new ResultDto(); public SystemController(ILogger logger, IRedisRepository redisRepository, IConfigRepository configRepository, DefaultDbContext db) { _logger = logger; _redisRepository = redisRepository; _configRepository = configRepository; _db = db; } // Config 값 JSON으로 반환 [HttpGet("configs")] public async Task Configs() { try { var config = await _redisRepository.GetObjectAsync>(RedisConst.ConfigKey); if (config is null) { config = _configRepository.GetAll(); await _redisRepository.SetObjectAsync(RedisConst.ConfigKey, config, RedisConst.CacheExpiration); } _result.Data = config; } catch (Exception e) { _logger.LogError(e, e.Message); _result.Ok = false; _result.Status = StatusCodes.Status400BadRequest; _result.Message = e.Message; } return _result; } // 문서 조회 [HttpGet("docs/{code}")] public async Task Docs([FromRoute] string code) { try { string cacheKey = $"{RedisConst.DocumentKey}-{code}"; var document = await _redisRepository.GetObjectAsync(cacheKey); if (document is null) { document = await _db.Document.Where(c => c.Code == code && c.IsActive) .Select(c => new { c.ID, c.Code, c.Subject, c.Content }) .FirstOrDefaultAsync(); await _redisRepository.SetObjectAsync(cacheKey, document, RedisConst.CacheExpiration); } if (document is null) { throw new Exception("문서 정보가 존재하지 않습니다."); } _result.Data = document; } catch (Exception e) { _logger.LogError(e, e.Message); _result.Ok = false; _result.Status = StatusCodes.Status400BadRequest; _result.Message = e.Message; } return _result; } // FAQ 분류 목록 [HttpGet("faq/categories")] public async Task FaqCategories() { try { string cacheKey = "FaqCategories"; var faqCategories = await _redisRepository.GetObjectAsync(cacheKey); if (faqCategories is null) { faqCategories = await _db.FaqCategory.Where(c => c.IsActive) .OrderBy(c => c.Order) .Select(c => new { c.ID, c.Code, c.Subject }).ToListAsync(); await _redisRepository.SetObjectAsync(cacheKey, faqCategories, RedisConst.CacheExpiration); } if (faqCategories is null) { throw new Exception("FAQ 분류 목록이 존재하지 않습니다."); } _result.Data = faqCategories; } catch (Exception e) { _logger.LogError(e, e.Message); _result.Ok = false; _result.Status = StatusCodes.Status400BadRequest; _result.Message = e.Message; } return _result; } // FAQ 분류별 자주 묻는 질문 목록 [HttpGet("faq/{code}")] public async Task Faq([FromRoute] string code, string? search = null) { try { string cacheKey = $"{RedisConst.FaqKey}-{code}"; await _redisRepository.DeleteAsync(cacheKey); var faqItems = await _redisRepository.GetObjectAsync>(cacheKey); if (faqItems == null) { var query = _db.FaqItem .Include(f => f.FaqCategory) .Where(f => f.FaqCategory.Code == code && f.FaqCategory.IsActive && f.IsActive); if (!string.IsNullOrEmpty(search)) { query = query.Where(f => f.Question.Contains(search) || f.Answer.Contains(search)); } faqItems = await query .OrderBy(f => f.Order) .Select(f => (object)new { f.ID, f.Question, f.Answer, f.Order, f.IsActive, CategoryCode = f.FaqCategory.Code, CategorySubject = f.FaqCategory.Subject }) .ToListAsync(); await _redisRepository.SetObjectAsync(cacheKey, faqItems, RedisConst.CacheExpiration); } if (faqItems == null || !faqItems.Any()) { throw new Exception("해당하는 FAQ 목록이 존재하지 않습니다."); } _result.Data = faqItems; } catch (Exception e) { _logger.LogError(e, e.Message); _result.Ok = false; _result.Status = StatusCodes.Status400BadRequest; _result.Message = e.Message; } return _result; } // 팝업 조회 [HttpGet("popup")] public async Task Popup() { try { string cacheKey = RedisConst.PopupKey; var popups = await _redisRepository.GetObjectAsync>(cacheKey); if (popups is null) { var now = DateTime.UtcNow; popups = await _db.Popup.Where(c => c.IsActive && (c.StartAt == null || c.StartAt <= now) && (c.EndAt == null || c.EndAt.Value.Date >= now.Date)) .OrderBy(c => c.Order) .Select(c => (object)new { c.ID, c.Subject, c.Content, c.Link }) .ToListAsync(); await _redisRepository.SetObjectAsync(cacheKey, popups, RedisConst.CacheExpiration); } if (popups is null) { throw new Exception("팝업이 존재하지 않습니다."); } _result.Data = popups; } catch (Exception e) { _logger.LogError(e, e.Message); _result.Ok = false; _result.Status = StatusCodes.Status400BadRequest; _result.Message = e.Message; } return _result; } [HttpGet("banner/{code}")] public async Task Banner([FromRoute] string code) { try { string cacheKey = RedisConst.BannerKey; var banners = await _redisRepository.GetObjectAsync>(cacheKey); if (banners == null) { var now = DateTime.UtcNow; banners = await _db.BannerItem .Include(c => c.BannerPosition) .Where(c => c.BannerPosition.Code == code && c.BannerPosition.IsActive && (c.StartAt == null || c.StartAt <= now) && (c.EndAt == null || c.EndAt.Value.Date >= now.Date)) .Where(c => c.IsActive) .OrderBy(c => c.Order) .Select(c => (object)new { c.ID, c.Subject, c.Image, c.Width, c.Height, c.Link, PositionID = c.BannerPosition.Code, PositionName = c.BannerPosition.Subject }) .ToListAsync(); await _redisRepository.SetObjectAsync(cacheKey, banners, RedisConst.CacheExpiration); } if (banners == null || !banners.Any()) { throw new Exception("해당하는 배너가 존재하지 않습니다."); } _result.Data = banners; } catch (Exception e) { _logger.LogError(e, e.Message); _result.Ok = false; _result.Status = StatusCodes.Status400BadRequest; _result.Message = e.Message; } return _result; } } }