From dc0d252df8b03c46e13334b5464f292a725b11a1 Mon Sep 17 00:00:00 2001 From: baeyc0510 Date: Wed, 12 Nov 2025 14:59:14 +0900 Subject: [PATCH] refactor: change UserRule 'no' field to 'id' with string type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the 'no' field with 'id' across the codebase for better clarity and consistency. Changed from int to string type to align with linter configuration requirements. Changes: - Update UserRule schema: no (int) -> id (string) - Update policy-editor.js: all references from 'no' to 'id' - Update policy validation in manager.go - Update all template JSON files with string-typed id values - Remove parseInt() calls in JavaScript, use string comparison πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- internal/policy/manager.go | 12 +++---- internal/policy/templates/go-template.json | 20 +++++------ internal/policy/templates/node-template.json | 20 +++++------ .../policy/templates/python-template.json | 20 +++++------ internal/policy/templates/react-template.json | 20 +++++------ .../policy/templates/typescript-template.json | 20 +++++------ internal/policy/templates/vue-template.json | 20 +++++------ internal/server/static/policy-editor.js | 34 +++++++++---------- pkg/schema/types.go | 3 +- 9 files changed, 84 insertions(+), 85 deletions(-) diff --git a/internal/policy/manager.go b/internal/policy/manager.go index 0515f66..52777c8 100644 --- a/internal/policy/manager.go +++ b/internal/policy/manager.go @@ -92,18 +92,18 @@ func ValidatePolicy(policy *schema.UserPolicy) error { } // Validate rules - ruleNumbers := make(map[int]bool) + ruleIDs := make(map[string]bool) for i, rule := range policy.Rules { if rule.Say == "" { return fmt.Errorf("rule %d: 'say' field is required", i+1) } - if rule.No <= 0 { - return fmt.Errorf("rule %d: 'no' field must be positive", i+1) + if rule.ID == "" { + return fmt.Errorf("rule %d: 'id' field is required", i+1) } - if ruleNumbers[rule.No] { - return fmt.Errorf("duplicate rule number: %d", rule.No) + if ruleIDs[rule.ID] { + return fmt.Errorf("duplicate rule id: %s", rule.ID) } - ruleNumbers[rule.No] = true + ruleIDs[rule.ID] = true } // Validate RBAC roles diff --git a/internal/policy/templates/go-template.json b/internal/policy/templates/go-template.json index 4c01723..884c81f 100644 --- a/internal/policy/templates/go-template.json +++ b/internal/policy/templates/go-template.json @@ -15,54 +15,54 @@ }, "rules": [ { - "no": 1, + "id": "1", "say": "νŒ¨ν‚€μ§€ 이름은 μ†Œλ¬Έμž ν•œ λ‹¨μ–΄λ‘œ μž‘μ„±ν•©λ‹ˆλ‹€ (예: http, json, user)", "category": "naming", "example": "// βœ… 쒋은 예:\npackage user\npackage database\npackage httputil\n\n// ❌ λ‚˜μœ 예:\npackage UserManagement\npackage data_base\npackage HTTP_Util" }, { - "no": 2, + "id": "2", "say": "λ³€μˆ˜μ™€ ν•¨μˆ˜ 이름은 camelCase λ˜λŠ” PascalCaseλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€ (public은 λŒ€λ¬Έμž μ‹œμž‘)", "category": "naming" }, { - "no": 3, + "id": "3", "say": "μ—λŸ¬λŠ” 항상 μ²˜λ¦¬ν•˜κ³ , λ¬΄μ‹œν•˜λŠ” 경우 λͺ…μ‹œμ μœΌλ‘œ _ λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€", "category": "error_handling", "example": "// βœ… 쒋은 예:\nfile, err := os.Open(\"config.json\")\nif err != nil {\n return fmt.Errorf(\"failed to open config: %w\", err)\n}\ndefer file.Close()\n\n// ❌ λ‚˜μœ 예:\nfile, _ := os.Open(\"config.json\")\ndefer file.Close()" }, { - "no": 4, + "id": "4", "say": "ν•¨μˆ˜λŠ” μ—λŸ¬λ₯Ό λ§ˆμ§€λ§‰ λ°˜ν™˜κ°’μœΌλ‘œ λ°˜ν™˜ν•©λ‹ˆλ‹€", "category": "error_handling" }, { - "no": 5, + "id": "5", "say": "μΈν„°νŽ˜μ΄μŠ€ 이름은 -er 접미사λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€ (예: Reader, Writer, Handler)", "category": "naming" }, { - "no": 6, + "id": "6", "say": "gofmt둜 μ½”λ“œλ₯Ό ν¬λ§·νŒ…ν•©λ‹ˆλ‹€", "category": "formatting" }, { - "no": 7, + "id": "7", "say": "곡개(exported) ν•¨μˆ˜μ™€ νƒ€μž…μ—λŠ” 주석을 μž‘μ„±ν•©λ‹ˆλ‹€", "category": "documentation" }, { - "no": 8, + "id": "8", "say": "context.Contextλ₯Ό ν•¨μˆ˜μ˜ 첫 번째 νŒŒλΌλ―Έν„°λ‘œ μ „λ‹¬ν•©λ‹ˆλ‹€", "category": "error_handling" }, { - "no": 9, + "id": "9", "say": "goroutine μ‚¬μš© μ‹œ μ μ ˆν•œ 동기화(sync, channel)λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€", "category": "error_handling" }, { - "no": 10, + "id": "10", "say": "deferλ₯Ό μ‚¬μš©ν•˜μ—¬ λ¦¬μ†ŒμŠ€ 정리λ₯Ό 보μž₯ν•©λ‹ˆλ‹€", "category": "error_handling" } diff --git a/internal/policy/templates/node-template.json b/internal/policy/templates/node-template.json index d8ef657..43166ca 100644 --- a/internal/policy/templates/node-template.json +++ b/internal/policy/templates/node-template.json @@ -19,55 +19,55 @@ }, "rules": [ { - "no": 1, + "id": "1", "say": "ν™˜κ²½ λ³€μˆ˜λŠ” process.envκ°€ μ•„λ‹Œ μ„€μ • 파일(config.js)을 톡해 μ ‘κ·Όν•©λ‹ˆλ‹€", "category": "security" }, { - "no": 2, + "id": "2", "say": "λ―Όκ°ν•œ 정보(API ν‚€, λΉ„λ°€λ²ˆν˜Έ)λŠ” μ ˆλŒ€ ν•˜λ“œμ½”λ”©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€", "category": "security", "example": "// βœ… 쒋은 예:\nconst apiKey = process.env.API_KEY;\nconst dbPassword = config.get('database.password');\n\n// ❌ λ‚˜μœ 예:\nconst apiKey = 'sk-1234567890abcdef';\nconst dbPassword = 'mySecretPassword123';" }, { - "no": 3, + "id": "3", "say": "λͺ¨λ“  비동기 ν•¨μˆ˜λŠ” try-catch λΈ”λ‘μœΌλ‘œ μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•©λ‹ˆλ‹€", "category": "error_handling", "example": "// βœ… 쒋은 예:\nasync function fetchUser(id) {\n try {\n const user = await db.users.findById(id);\n return user;\n } catch (error) {\n logger.error('Failed to fetch user:', error);\n throw new ApiError('User not found', 404);\n }\n}\n\n// ❌ λ‚˜μœ 예:\nasync function fetchUser(id) {\n const user = await db.users.findById(id);\n return user;\n}" }, { - "no": 4, + "id": "4", "say": "API 라우트 ν•Έλ“€λŸ¬λŠ” 항상 μ μ ˆν•œ HTTP μƒνƒœ μ½”λ“œλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€", "category": "error_handling" }, { - "no": 5, + "id": "5", "say": "λ°μ΄ν„°λ² μ΄μŠ€ μΏΌλ¦¬λŠ” SQL injection을 λ°©μ§€ν•˜κΈ° μœ„ν•΄ parameterized queryλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€", "category": "security", "example": "// βœ… 쒋은 예:\nconst result = await db.query('SELECT * FROM users WHERE id = ?', [userId]);\n\n// ❌ λ‚˜μœ 예:\nconst result = await db.query(`SELECT * FROM users WHERE id = ${userId}`);" }, { - "no": 6, + "id": "6", "say": "파일 이름은 kebab-caseλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€ (예: user-controller.js, auth-service.js)", "category": "naming" }, { - "no": 7, + "id": "7", "say": "클래슀 이름은 PascalCase, ν•¨μˆ˜/λ³€μˆ˜λŠ” camelCaseλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€", "category": "naming" }, { - "no": 8, + "id": "8", "say": "미듀웨어 ν•¨μˆ˜λŠ” next()λ₯Ό λͺ…μ‹œμ μœΌλ‘œ ν˜ΈμΆœν•˜κ±°λ‚˜ 응닡을 λ³΄λƒ…λ‹ˆλ‹€", "category": "error_handling" }, { - "no": 9, + "id": "9", "say": "큰 μ˜μ‘΄μ„± νŒ¨ν‚€μ§€λŠ” ν•„μš”ν•œ λΆ€λΆ„λ§Œ importν•©λ‹ˆλ‹€ (tree-shaking)", "category": "performance" }, { - "no": 10, + "id": "10", "say": "둜그 λ©”μ‹œμ§€λŠ” μ μ ˆν•œ 레벨(debug, info, warn, error)을 μ‚¬μš©ν•©λ‹ˆλ‹€", "category": "documentation" } diff --git a/internal/policy/templates/python-template.json b/internal/policy/templates/python-template.json index b89596f..af94337 100644 --- a/internal/policy/templates/python-template.json +++ b/internal/policy/templates/python-template.json @@ -19,55 +19,55 @@ }, "rules": [ { - "no": 1, + "id": "1", "say": "PEP 8 μŠ€νƒ€μΌ κ°€μ΄λ“œλ₯Ό μ€€μˆ˜ν•©λ‹ˆλ‹€ (λ“€μ—¬μ“°κΈ° 4μΉΈ, μ΅œλŒ€ 라인 길이 79자)", "category": "formatting" }, { - "no": 2, + "id": "2", "say": "ν•¨μˆ˜μ™€ λ³€μˆ˜ 이름은 snake_caseλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€ (예: calculate_total, user_name)", "category": "naming", "example": "# βœ… 쒋은 예:\ndef calculate_total_price(items):\n total_price = sum(item.price for item in items)\n return total_price\n\n# ❌ λ‚˜μœ 예:\ndef calculateTotalPrice(items):\n totalPrice = sum(item.price for item in items)\n return totalPrice" }, { - "no": 3, + "id": "3", "say": "클래슀 이름은 PascalCaseλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€ (예: UserModel, DataProcessor)", "category": "naming", "example": "# βœ… 쒋은 예:\nclass UserProfile:\n def __init__(self, name):\n self.name = name\n\n# ❌ λ‚˜μœ 예:\nclass user_profile:\n def __init__(self, name):\n self.name = name" }, { - "no": 4, + "id": "4", "say": "μƒμˆ˜λŠ” λŒ€λ¬Έμžμ™€ μ–Έλ”μŠ€μ½”μ–΄λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€ (예: MAX_SIZE, API_KEY)", "category": "naming" }, { - "no": 5, + "id": "5", "say": "λͺ¨λ“  ν•¨μˆ˜μ™€ ν΄λž˜μŠ€λŠ” docstring을 μž‘μ„±ν•©λ‹ˆλ‹€", "category": "documentation" }, { - "no": 6, + "id": "6", "say": "νƒ€μž… 힌트λ₯Ό μ‚¬μš©ν•˜μ—¬ ν•¨μˆ˜ μ‹œκ·Έλ‹ˆμ²˜λ₯Ό λͺ…ν™•νžˆ ν•©λ‹ˆλ‹€", "category": "documentation" }, { - "no": 7, + "id": "7", "say": "μ˜ˆμ™ΈλŠ” ꡬ체적인 νƒ€μž…μœΌλ‘œ μ²˜λ¦¬ν•©λ‹ˆλ‹€ (Exceptionλ³΄λ‹€λŠ” ValueError, TypeError λ“±)", "category": "error_handling", "example": "# βœ… 쒋은 예:\ntry:\n result = int(user_input)\nexcept ValueError as e:\n logger.error(f\"Invalid input: {e}\")\n result = 0\n\n# ❌ λ‚˜μœ 예:\ntry:\n result = int(user_input)\nexcept:\n result = 0" }, { - "no": 8, + "id": "8", "say": "λ¦¬μ†ŒμŠ€(파일, λ°μ΄ν„°λ² μ΄μŠ€)λŠ” with 문으둜 κ΄€λ¦¬ν•©λ‹ˆλ‹€", "category": "error_handling" }, { - "no": 9, + "id": "9", "say": "λ―Όκ°ν•œ μ •λ³΄λŠ” ν™˜κ²½ λ³€μˆ˜λ‚˜ μ„€μ • νŒŒμΌμ„ 톡해 κ΄€λ¦¬ν•©λ‹ˆλ‹€", "category": "security" }, { - "no": 10, + "id": "10", "say": "리슀트 μ»΄ν”„λ¦¬ν—¨μ…˜μ€ 3쀄 μ΄λ‚΄λ‘œ μœ μ§€ν•˜κ³ , λ³΅μž‘ν•œ 경우 일반 for문을 μ‚¬μš©ν•©λ‹ˆλ‹€", "category": "formatting" } diff --git a/internal/policy/templates/react-template.json b/internal/policy/templates/react-template.json index 3c16a26..82d0d6b 100644 --- a/internal/policy/templates/react-template.json +++ b/internal/policy/templates/react-template.json @@ -19,60 +19,60 @@ }, "rules": [ { - "no": 1, + "id": "1", "say": "μ»΄ν¬λ„ŒνŠΈ 이름은 PascalCaseλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€ (예: UserProfile, NavBar)", "category": "naming", "languages": ["javascript", "typescript", "jsx", "tsx"], "example": "// βœ… 쒋은 예:\nfunction UserProfile() { ... }\nfunction NavBar() { ... }\n\n// ❌ λ‚˜μœ 예:\nfunction userProfile() { ... }\nfunction nav_bar() { ... }" }, { - "no": 2, + "id": "2", "say": "React HooksλŠ” ν•¨μˆ˜ μ»΄ν¬λ„ŒνŠΈ μ΅œμƒλ‹¨μ—μ„œλ§Œ ν˜ΈμΆœν•©λ‹ˆλ‹€", "category": "error_handling", "languages": ["javascript", "typescript", "jsx", "tsx"], "example": "// βœ… 쒋은 예:\nfunction MyComponent() {\n const [count, setCount] = useState(0);\n const value = useMemo(() => count * 2, [count]);\n return
{value}
;\n}\n\n// ❌ λ‚˜μœ 예:\nfunction MyComponent() {\n if (condition) {\n const [count, setCount] = useState(0); // 쑰건뢀 호좜 κΈˆμ§€\n }\n}" }, { - "no": 3, + "id": "3", "say": "useEffect의 μ˜μ‘΄μ„± 배열을 μ •ν™•νžˆ λͺ…μ‹œν•˜μ—¬ λ¬΄ν•œ 루프λ₯Ό λ°©μ§€ν•©λ‹ˆλ‹€", "category": "error_handling", "languages": ["javascript", "typescript"] }, { - "no": 4, + "id": "4", "say": "PropsλŠ” ꡬ쑰 λΆ„ν•΄ ν• λ‹ΉμœΌλ‘œ λ°›μŠ΅λ‹ˆλ‹€ (예: function Button({ label, onClick }) {...})", "category": "formatting", "languages": ["javascript", "typescript", "jsx", "tsx"], "example": "// βœ… 쒋은 예:\nfunction Button({ label, onClick, disabled = false }) {\n return ;\n}\n\n// ❌ λ‚˜μœ 예:\nfunction Button(props) {\n return ;\n}" }, { - "no": 5, + "id": "5", "say": "μ»΄ν¬λ„ŒνŠΈ 파일 ν•˜λ‚˜λ‹Ή ν•˜λ‚˜μ˜ μ»΄ν¬λ„ŒνŠΈλ§Œ exportν•©λ‹ˆλ‹€", "category": "formatting" }, { - "no": 6, + "id": "6", "say": "이벀트 ν•Έλ“€λŸ¬ ν•¨μˆ˜λŠ” 'handle' 접두사λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€ (예: handleClick, handleSubmit)", "category": "naming" }, { - "no": 7, + "id": "7", "say": "boolean νƒ€μž…μ˜ PropsλŠ” 'is', 'has', 'should' 접두사λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€ (예: isOpen, hasError)", "category": "naming" }, { - "no": 8, + "id": "8", "say": "인라인 μŠ€νƒ€μΌ λŒ€μ‹  CSS λͺ¨λ“ˆ λ˜λŠ” styled-componentsλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€", "category": "formatting" }, { - "no": 9, + "id": "9", "say": "Key prop에 λ°°μ—΄ 인덱슀λ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. κ³ μœ ν•œ IDλ₯Ό μ‚¬μš©ν•˜μ„Έμš”", "category": "performance", "example": "// βœ… 쒋은 예:\n{users.map(user => )}\n\n// ❌ λ‚˜μœ 예:\n{users.map((user, index) => )}" }, { - "no": 10, + "id": "10", "say": "큰 λ¦¬μŠ€νŠΈλŠ” React.memo λ˜λŠ” useMemo둜 μ΅œμ ν™”ν•©λ‹ˆλ‹€", "category": "performance" } diff --git a/internal/policy/templates/typescript-template.json b/internal/policy/templates/typescript-template.json index cdf6b2d..f460f19 100644 --- a/internal/policy/templates/typescript-template.json +++ b/internal/policy/templates/typescript-template.json @@ -15,54 +15,54 @@ }, "rules": [ { - "no": 1, + "id": "1", "say": "any νƒ€μž… μ‚¬μš©μ„ μ΅œμ†Œν™”ν•˜κ³  ꡬ체적인 νƒ€μž…μ„ μ •μ˜ν•©λ‹ˆλ‹€", "category": "error_handling", "example": "// βœ… 쒋은 예:\ninterface User {\n id: number;\n name: string;\n}\nfunction processUser(user: User): void { ... }\n\n// ❌ λ‚˜μœ 예:\nfunction processUser(user: any): void { ... }" }, { - "no": 2, + "id": "2", "say": "μΈν„°νŽ˜μ΄μŠ€μ™€ νƒ€μž… λ³„μΉ­μ˜ 이름은 PascalCaseλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€", "category": "naming" }, { - "no": 3, + "id": "3", "say": "νƒ€μž… μ •μ˜ 파일(.d.ts)은 types/ 폴더에 μœ„μΉ˜μ‹œν‚΅λ‹ˆλ‹€", "category": "formatting" }, { - "no": 4, + "id": "4", "say": "μ œλ„€λ¦­ νƒ€μž… νŒŒλΌλ―Έν„°λŠ” 의미 μžˆλŠ” 이름을 μ‚¬μš©ν•©λ‹ˆλ‹€ (Tλ³΄λ‹€λŠ” TItem, TResponse)", "category": "naming" }, { - "no": 5, + "id": "5", "say": "μœ ν‹Έλ¦¬ν‹° νƒ€μž…(Partial, Pick, Omit λ“±)을 적극 ν™œμš©ν•©λ‹ˆλ‹€", "category": "performance" }, { - "no": 6, + "id": "6", "say": "strict λͺ¨λ“œλ₯Ό ν™œμ„±ν™”ν•˜μ—¬ μ—„κ²©ν•œ νƒ€μž… 체크λ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€", "category": "error_handling" }, { - "no": 7, + "id": "7", "say": "μ—΄κ±°ν˜•(enum) λŒ€μ‹  const assertionμ΄λ‚˜ union νƒ€μž…μ„ κ³ λ €ν•©λ‹ˆλ‹€", "category": "performance" }, { - "no": 8, + "id": "8", "say": "ν•¨μˆ˜ μ‹œκ·Έλ‹ˆμ²˜μ— λ°˜ν™˜ νƒ€μž…μ„ λͺ…μ‹œμ μœΌλ‘œ μ„ μ–Έν•©λ‹ˆλ‹€", "category": "documentation", "example": "// βœ… 쒋은 예:\nfunction calculateTotal(price: number, quantity: number): number {\n return price * quantity;\n}\n\n// ❌ λ‚˜μœ 예:\nfunction calculateTotal(price: number, quantity: number) {\n return price * quantity;\n}" }, { - "no": 9, + "id": "9", "say": "readonlyλ₯Ό μ‚¬μš©ν•˜μ—¬ λΆˆλ³€μ„±μ„ 보μž₯ν•©λ‹ˆλ‹€", "category": "error_handling" }, { - "no": 10, + "id": "10", "say": "νƒ€μž… κ°€λ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ λŸ°νƒ€μž„ νƒ€μž… μ•ˆμ „μ„±μ„ ν™•λ³΄ν•©λ‹ˆλ‹€", "category": "error_handling" } diff --git a/internal/policy/templates/vue-template.json b/internal/policy/templates/vue-template.json index 820a535..2dd1f9d 100644 --- a/internal/policy/templates/vue-template.json +++ b/internal/policy/templates/vue-template.json @@ -15,54 +15,54 @@ }, "rules": [ { - "no": 1, + "id": "1", "say": "μ»΄ν¬λ„ŒνŠΈ 이름은 PascalCaseλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€ (예: UserProfile.vue)", "category": "naming", "example": "\n\n\n\n" }, { - "no": 2, + "id": "2", "say": "Composition APIλ₯Ό μ‚¬μš©ν•  λ•Œ setup() ν•¨μˆ˜ λ˜λŠ” \n\n\n" }, { - "no": 3, + "id": "3", "say": "PropsλŠ” νƒ€μž…κ³Ό 기본값을 λͺ…μ‹œν•©λ‹ˆλ‹€", "category": "documentation" }, { - "no": 4, + "id": "4", "say": "이벀트 이름은 kebab-caseλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€ (예: @user-updated, @item-deleted)", "category": "naming" }, { - "no": 5, + "id": "5", "say": "v-forμ—λŠ” 항상 :keyλ₯Ό μ§€μ •ν•©λ‹ˆλ‹€", "category": "error_handling" }, { - "no": 6, + "id": "6", "say": "computedλŠ” λΆ€μˆ˜ 효과 없이 순수 ν•¨μˆ˜λ‘œ μž‘μ„±ν•©λ‹ˆλ‹€", "category": "performance" }, { - "no": 7, + "id": "7", "say": "composables ν•¨μˆ˜ 이름은 'use' 접두사λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€ (예: useUser, useFetch)", "category": "naming" }, { - "no": 8, + "id": "8", "say": "ν…œν”Œλ¦Ώ λ‚΄ λ³΅μž‘ν•œ ν‘œν˜„μ‹μ€ computed μ†μ„±μœΌλ‘œ λΆ„λ¦¬ν•©λ‹ˆλ‹€", "category": "formatting" }, { - "no": 9, + "id": "9", "say": "κΈ€λ‘œλ²Œ μƒνƒœλŠ” Pinia λ˜λŠ” Vuexλ₯Ό μ‚¬μš©ν•˜μ—¬ κ΄€λ¦¬ν•©λ‹ˆλ‹€", "category": "formatting" }, { - "no": 10, + "id": "10", "say": "μ»΄ν¬λ„ŒνŠΈλŠ” 단일 μ±…μž„ 원칙을 λ”°λ₯΄λ©° 200쀄 μ΄ν•˜λ‘œ μœ μ§€ν•©λ‹ˆλ‹€", "category": "formatting" } diff --git a/internal/server/static/policy-editor.js b/internal/server/static/policy-editor.js index 07671f5..5794c8b 100644 --- a/internal/server/static/policy-editor.js +++ b/internal/server/static/policy-editor.js @@ -381,27 +381,27 @@ function renderRules() { } function createRuleElement(rule, index) { - const actualIndex = appState.policy.rules.findIndex(r => r.no === rule.no); + const actualIndex = appState.policy.rules.findIndex(r => r.id === rule.id); return ` -
+
- ${rule.no}. + ${rule.id}. ${rule.say || 'μƒˆ κ·œμΉ™ (λ‚΄μš©μ„ μž…λ ₯ν•˜μ„Έμš”)'}
- +
- +
- ${Object.keys(CATEGORY_COLORS).filter(c => c !== 'default').map(cat => ` @@ -410,12 +410,12 @@ function createRuleElement(rule, index) {
- +
- +
@@ -424,11 +424,11 @@ function createRuleElement(rule, index) { } function handleRuleUpdate(e) { - const ruleNo = parseInt(e.target.dataset.ruleNo); - const rule = appState.policy.rules.find(r => r.no === ruleNo); + const ruleId = e.target.dataset.ruleId; + const rule = appState.policy.rules.find(r => r.id === ruleId); if (!rule) return; - const ruleElement = document.querySelector(`.rule-details[data-rule-no="${ruleNo}"]`); + const ruleElement = document.querySelector(`.rule-details[data-rule-id="${ruleId}"]`); if (e.target.classList.contains('say-input')) { rule.say = e.target.value.trim(); @@ -448,15 +448,15 @@ function handleRuleUpdate(e) { function handleDeleteRule(e) { e.preventDefault(); - const ruleNo = parseInt(e.target.dataset.ruleNo); + const ruleId = e.target.dataset.ruleId; if (!confirm('이 κ·œμΉ™μ„ 정말 μ‚­μ œν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?')) return; - appState.policy.rules = appState.policy.rules.filter(r => r.no !== ruleNo); + appState.policy.rules = appState.policy.rules.filter(r => r.id !== ruleId); // Renumber rules appState.policy.rules.forEach((rule, index) => { - rule.no = index + 1; + rule.id = String(index + 1); }); renderRules(); @@ -465,14 +465,14 @@ function handleDeleteRule(e) { } function handleAddRule() { - const newNo = appState.policy.rules.length + 1; - const newRule = { no: newNo, say: '', category: '', languages: [], example: '' }; + const newId = String(appState.policy.rules.length + 1); + const newRule = { id: newId, say: '', category: '', languages: [], example: '' }; appState.policy.rules.push(newRule); renderRules(); // Open the new rule details setTimeout(() => { - const newRuleElement = document.querySelector(`.rule-details[data-rule-no="${newNo}"]`); + const newRuleElement = document.querySelector(`.rule-details[data-rule-id="${newId}"]`); if (newRuleElement) { newRuleElement.setAttribute('open', ''); newRuleElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); diff --git a/pkg/schema/types.go b/pkg/schema/types.go index e8f5c5e..a3bba37 100644 --- a/pkg/schema/types.go +++ b/pkg/schema/types.go @@ -33,8 +33,7 @@ type UserDefaults struct { // UserRule represents a single rule in user schema type UserRule struct { - No int `json:"no,omitempty"` // symphonyclient integration: rule number for ordering - ID string `json:"id,omitempty"` + ID string `json:"id,omitempty"` // symphonyclient integration: rule number for ordering Say string `json:"say"` Category string `json:"category,omitempty"` Languages []string `json:"languages,omitempty"`