191 lines
6.8 KiB
Markdown
191 lines
6.8 KiB
Markdown
# STEP 8 — 컨트롤러 추가 (`ExperionFastController`)
|
|
|
|
## 사전 확인 (작업 전 반드시 수행)
|
|
|
|
1. `src/Web/Controllers/ExperionControllers.cs` 파일을 열어 전체 내용을 읽는다.
|
|
2. 아래 항목을 확인하고 기록한다:
|
|
- [x] STEP 7이 완료되어 `ExperionFastService`가 빌드되는가?
|
|
- [x] `ExperionFastController` 클래스가 이미 존재하는가? → 있으면 내용 비교 후 누락 엔드포인트만 추가
|
|
- [x] `PinRequest` record가 이미 존재하는가?
|
|
- [x] 파일 상단에 `using Microsoft.AspNetCore.Mvc;` 가 있는가?
|
|
|
|
---
|
|
|
|
## 작업 내용
|
|
|
|
**파일**: `src/Web/Controllers/ExperionControllers.cs`
|
|
**위치**: 파일 하단 (기존 컨트롤러 마지막 클래스 아래)
|
|
|
|
```csharp
|
|
// ── FastTable / FastRecord ────────────────────────────────────────────────────
|
|
|
|
[ApiController]
|
|
[Route("api/fast")]
|
|
public class ExperionFastController : ControllerBase
|
|
{
|
|
private readonly IExperionFastService _fastSvc;
|
|
|
|
public ExperionFastController(IExperionFastService fastSvc)
|
|
=> _fastSvc = fastSvc;
|
|
|
|
/// <summary>새 fastSession 시작</summary>
|
|
[HttpPost("start")]
|
|
public async Task<IActionResult> Start([FromBody] FastSessionStartRequest request)
|
|
{
|
|
try
|
|
{
|
|
var session = await _fastSvc.StartSessionAsync(request);
|
|
return Ok(new { id = session.Id, name = session.Name, status = session.Status, startedAt = session.StartedAt });
|
|
}
|
|
catch (ArgumentException ex) { return BadRequest(new { error = ex.Message }); }
|
|
catch (InvalidOperationException ex) { return Conflict(new { error = ex.Message }); }
|
|
}
|
|
|
|
/// <summary>세션 중지</summary>
|
|
[HttpPost("{id:int}/stop")]
|
|
public async Task<IActionResult> Stop(int id)
|
|
{
|
|
try
|
|
{
|
|
await _fastSvc.StopSessionAsync(id);
|
|
return Ok(new { success = true, message = "세션이 중지되었습니다." });
|
|
}
|
|
catch (InvalidOperationException ex) { return NotFound(new { error = ex.Message }); }
|
|
}
|
|
|
|
/// <summary>세션 목록 조회</summary>
|
|
[HttpGet("sessions")]
|
|
public async Task<IActionResult> GetSessions()
|
|
{
|
|
var sessions = await _fastSvc.GetSessionsAsync();
|
|
return Ok(new
|
|
{
|
|
total = sessions.Count(),
|
|
items = sessions.Select(s => new
|
|
{
|
|
id = s.Id,
|
|
name = s.Name,
|
|
status = s.Status,
|
|
samplingMs = s.SamplingMs,
|
|
durationSec = s.DurationSec,
|
|
tagCount = s.TagList.Length,
|
|
rowCount = s.RowCount,
|
|
startedAt = s.StartedAt,
|
|
endedAt = s.EndedAt,
|
|
retentionDays = s.RetentionDays,
|
|
pinned = s.Pinned
|
|
})
|
|
});
|
|
}
|
|
|
|
/// <summary>세션 상세 정보</summary>
|
|
[HttpGet("{id:int}")]
|
|
public async Task<IActionResult> GetSession(int id)
|
|
{
|
|
var session = await _fastSvc.GetSessionAsync(id);
|
|
if (session == null) return NotFound();
|
|
return Ok(new
|
|
{
|
|
id = session.Id,
|
|
name = session.Name,
|
|
status = session.Status,
|
|
samplingMs = session.SamplingMs,
|
|
durationSec = session.DurationSec,
|
|
tagList = session.TagList,
|
|
rowCount = session.RowCount,
|
|
startedAt = session.StartedAt,
|
|
endedAt = session.EndedAt,
|
|
retentionDays = session.RetentionDays,
|
|
pinned = session.Pinned
|
|
});
|
|
}
|
|
|
|
/// <summary>레코드 조회 (Long 포맷)</summary>
|
|
[HttpGet("{id:int}/records")]
|
|
public async Task<IActionResult> GetRecords(int id,
|
|
[FromQuery] DateTime? from,
|
|
[FromQuery] DateTime? to,
|
|
[FromQuery] string format = "long")
|
|
{
|
|
var result = await _fastSvc.GetRecordsAsync(id, from, to, format);
|
|
return Ok(new
|
|
{
|
|
sessionId = result.SessionId,
|
|
from = result.From,
|
|
to = result.To,
|
|
tagNames = result.TagNames,
|
|
total = result.TotalCount,
|
|
items = result.Items.Select(r => new
|
|
{
|
|
sessionId = r.SessionId,
|
|
recordedAt = r.RecordedAt,
|
|
tagName = r.TagName,
|
|
value = r.Value
|
|
})
|
|
});
|
|
}
|
|
|
|
/// <summary>CSV Export (스트리밍)</summary>
|
|
[HttpGet("{id:int}/csv")]
|
|
public async Task<IActionResult> ExportCsv(int id,
|
|
[FromQuery] DateTime? from,
|
|
[FromQuery] DateTime? to)
|
|
{
|
|
var ms = new MemoryStream();
|
|
await _fastSvc.ExportCsvAsync(id, ms, from, to);
|
|
ms.Position = 0;
|
|
return File(ms, "text/csv", $"fast-{id}-{DateTime.Now:yyyyMMddHHmm}.csv");
|
|
}
|
|
|
|
/// <summary>세션 삭제</summary>
|
|
[HttpDelete("{id:int}")]
|
|
public async Task<IActionResult> Delete(int id)
|
|
{
|
|
try
|
|
{
|
|
await _fastSvc.DeleteSessionAsync(id);
|
|
return Ok(new { success = true, message = "세션이 삭제되었습니다." });
|
|
}
|
|
catch (InvalidOperationException ex) { return NotFound(new { error = ex.Message }); }
|
|
}
|
|
|
|
/// <summary>세션 고정/해제</summary>
|
|
[HttpPost("{id:int}/pin")]
|
|
public async Task<IActionResult> Pin(int id, [FromBody] PinRequest request)
|
|
{
|
|
try
|
|
{
|
|
await _fastSvc.PinSessionAsync(id, request.Pinned);
|
|
return Ok(new { success = true, pinned = request.Pinned });
|
|
}
|
|
catch (InvalidOperationException ex) { return NotFound(new { error = ex.Message }); }
|
|
}
|
|
}
|
|
|
|
public record PinRequest(bool Pinned);
|
|
```
|
|
|
|
---
|
|
|
|
## 사후 확인 (작업 후 반드시 수행)
|
|
|
|
1. `ExperionControllers.cs` 파일을 다시 열어 추가된 컨트롤러를 읽는다.
|
|
2. 아래 항목을 하나씩 확인한다:
|
|
- [x] `[Route("api/fast")]` 라우트가 맞는가?
|
|
- [x] `POST /start` 엔드포인트 존재
|
|
- [x] `POST /{id}/stop` 엔드포인트 존재
|
|
- [x] `GET /sessions` 엔드포인트 존재
|
|
- [x] `GET /{id}` 엔드포인트 존재
|
|
- [x] `GET /{id}/records` 엔드포인트 존재
|
|
- [x] `GET /{id}/csv` 엔드포인트 존재
|
|
- [x] `DELETE /{id}` 엔드포인트 존재
|
|
- [x] `POST /{id}/pin` 엔드포인트 존재 (총 8개 엔드포인트)
|
|
- [x] `PinRequest` record 존재 (중복 선언 아닌지 확인)
|
|
3. `dotnet build src/Web` 실행 → 에러/경고 0개 확인
|
|
4. 문제가 있으면 수정 후 다시 빌드 확인
|
|
|
|
---
|
|
|
|
## 완료 조건
|
|
- `dotnet build src/Web` 결과: 에러 0, 경고 0
|
|
- `ExperionFastController` 8개 엔드포인트 모두 존재 |