Major project initialization and feature implementation: **Core Features:** - OPC UA client for Honeywell Experion HS R530 integration - Real-time data streaming and history data retrieval - Text-to-SQL query engine with TimeScaleDB - JSON-based node configuration system - SQLite database with migration support **Architecture:** - Clean architecture with Domain, Application, Infrastructure layers - ASP.NET Core Web API frontend - Web UI with real-time visualization - PKI-based OPC UA authentication (TLS) **Infrastructure Components:** - ExperionOpcClient: OPC UA connection management - ExperionRealtimeService: Real-time data streaming - ExperionHistoryService: Historical data queries - TextToSqlService: Natural language to SQL queries - SqlValidator: SQL injection prevention **Database:** - TimescaleDB integration (recommended) or SQLite fallback - Entity Framework Core with Extenstion methods - OPCTag, KeyValue tables for data storage **Security:** - Certificate-based OPC UA endpoint security - SSL/TLS encryption for database connections - Output param binding injection prevention **Testing:** - Unit tests for TextToSqlService and SqlValidator - Integration tests for Korean time range extraction See REVIEW_REQUEST.md for detailed code review information.
111 lines
2.8 KiB
Markdown
111 lines
2.8 KiB
Markdown
# Roo 작업 지시: 노드맵 대시보드 undefined 필드 수정
|
|
|
|
## 배경 및 원인
|
|
|
|
`src/Web/Program.cs` 에 다음 설정이 있음:
|
|
```csharp
|
|
opt.JsonSerializerOptions.PropertyNamingPolicy = null; // PascalCase 직렬화
|
|
```
|
|
|
|
이로 인해 C# 익명 객체 shorthand `new { x.Id, x.NodeId }` 등은 PascalCase로 직렬화됨.
|
|
프론트엔드(`app.js`)는 camelCase(`r.id`, `r.nodeId`)로 접근 → **모든 값이 `undefined`로 표시됨**.
|
|
|
|
같은 문제를 Browse 엔드포인트에서도 확인했으며, 명시적 camelCase 익명 객체로 수정 완료:
|
|
```csharp
|
|
// 수정 전
|
|
return Ok(new { success = r.Success, nodes = r.Nodes, error = r.ErrorMessage });
|
|
|
|
// 수정 후
|
|
return Ok(new {
|
|
success = r.Success,
|
|
error = r.ErrorMessage,
|
|
nodes = r.Nodes.Select(n => new {
|
|
nodeId = n.NodeId,
|
|
displayName = n.DisplayName,
|
|
nodeClass = n.NodeClass,
|
|
hasChildren = n.HasChildren
|
|
})
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 수정 대상 파일
|
|
|
|
**`src/Web/Controllers/ExperionControllers.cs`**
|
|
클래스: `ExperionNodeMapController`
|
|
메서드: `Query()` (약 571번째 줄)
|
|
|
|
---
|
|
|
|
## 현재 코드 (문제)
|
|
|
|
```csharp
|
|
return Ok(new
|
|
{
|
|
total = r.Total,
|
|
items = r.Items.Select(x => new
|
|
{
|
|
x.Id, x.Level, x.Class, x.Name, x.NodeId, x.DataType
|
|
})
|
|
});
|
|
```
|
|
|
|
`x.Id` → JSON `"Id"` (PascalCase) → JS `r.id` = undefined
|
|
|
|
---
|
|
|
|
## 수정 후 코드 (목표)
|
|
|
|
```csharp
|
|
return Ok(new
|
|
{
|
|
total = r.Total,
|
|
items = r.Items.Select(x => new
|
|
{
|
|
id = x.Id,
|
|
level = x.Level,
|
|
@class = x.Class,
|
|
name = x.Name,
|
|
nodeId = x.NodeId,
|
|
dataType = x.DataType
|
|
})
|
|
});
|
|
```
|
|
|
|
`@class` 는 C# 예약어 회피용이며, JSON 직렬화 시 `"class"` 로 정상 출력됨.
|
|
|
|
---
|
|
|
|
## 추가 확인 사항 (같은 패턴이 있는지 전수 검사)
|
|
|
|
`ExperionControllers.cs` 전체에서 `PropertyNamingPolicy = null` 환경에서 PascalCase로 직렬화될 수 있는 패턴을 모두 찾아 수정:
|
|
|
|
1. `new { x.PropertyName }` 형태의 shorthand 익명 객체
|
|
2. 직접 typed record/class 인스턴스를 `Ok(...)` 에 넣는 경우
|
|
|
|
단, 다음은 이미 lowercase이므로 수정 불필요:
|
|
- `new { success = ..., nodes = ... }` — 명시적 소문자 키
|
|
- `new { total = ..., names = ... }` — 명시적 소문자 키
|
|
|
|
---
|
|
|
|
## 빌드 검증
|
|
|
|
수정 후 반드시:
|
|
```bash
|
|
dotnet build src/Web/ExperionCrawler.csproj --no-restore -v q
|
|
```
|
|
- `Build succeeded` 확인
|
|
- 에러 0건 확인
|
|
|
|
---
|
|
|
|
## 클로드 코드 검수 항목
|
|
|
|
수정 완료 후 아래 내용을 검수 요청:
|
|
1. `ExperionNodeMapController.Query()` 응답 필드가 모두 camelCase인지
|
|
2. 같은 패턴(`{ x.Prop }`)이 다른 컨트롤러에도 있는지 확인 여부
|
|
3. 빌드 성공 여부
|
|
4. `@class` → JSON `"class"` 직렬화 정상 작동 여부
|