resource_controller.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. package controllers
  2. import (
  3. "fmt"
  4. "ems-backend/models"
  5. "encoding/json"
  6. "net/http"
  7. "sort"
  8. "time"
  9. "github.com/gin-gonic/gin"
  10. "github.com/google/uuid"
  11. "gorm.io/datatypes"
  12. "bytes"
  13. "io"
  14. "strings"
  15. )
  16. // --- Integration Source Controllers ---
  17. type HAConfig struct {
  18. URL string `json:"url"`
  19. Token string `json:"token"`
  20. }
  21. type HAEntity struct {
  22. EntityID string `json:"entity_id"`
  23. State string `json:"state"`
  24. Attributes map[string]interface{} `json:"attributes"`
  25. LastChanged time.Time `json:"last_changed"`
  26. LastUpdated time.Time `json:"last_updated"`
  27. DeviceID string `json:"device_id"` // Augmented field
  28. DeviceName string `json:"device_name"` // Augmented field
  29. }
  30. // HA Template Request
  31. type HATemplateReq struct {
  32. Template string `json:"template"`
  33. }
  34. // Struct for template result parsing
  35. type HATemplateResult struct {
  36. ID string `json:"id"`
  37. State string `json:"s"`
  38. Name string `json:"n"`
  39. DID string `json:"did"`
  40. DName string `json:"dn"`
  41. }
  42. // HADevice represents a Home Assistant Device
  43. type HADevice struct {
  44. ID string `json:"id"`
  45. Name string `json:"name"`
  46. Model string `json:"model"`
  47. Manufacturer string `json:"manufacturer"`
  48. }
  49. func fetchHADevices(config datatypes.JSON) ([]HADevice, error) {
  50. var haConfig HAConfig
  51. b, err := config.MarshalJSON()
  52. if err != nil {
  53. return nil, fmt.Errorf("config error: %v", err)
  54. }
  55. if err := json.Unmarshal(b, &haConfig); err != nil {
  56. return nil, fmt.Errorf("invalid configuration format: %v", err)
  57. }
  58. if haConfig.URL == "" || haConfig.Token == "" {
  59. return nil, fmt.Errorf("URL and Token are required")
  60. }
  61. client := &http.Client{Timeout: 10 * time.Second}
  62. url := haConfig.URL
  63. // Robust URL handling: remove trailing slash and /api suffix
  64. url = strings.TrimSuffix(url, "/")
  65. url = strings.TrimSuffix(url, "/api")
  66. // Use Template API to get devices efficiently
  67. // Simplified template avoiding list.append due to sandbox restrictions
  68. template := `
  69. {% set ns = namespace(result=[], devs=[]) %}
  70. {% for state in states %}
  71. {% set d = device_id(state.entity_id) %}
  72. {% if d and d not in ns.devs %}
  73. {% set ns.devs = ns.devs + [d] %}
  74. {% set name = device_attr(d, 'name_by_user') %}
  75. {% if not name %}
  76. {% set name = device_attr(d, 'name') %}
  77. {% endif %}
  78. {% if not name %}
  79. {% set name = 'Unknown' %}
  80. {% endif %}
  81. {% set entry = {
  82. "id": d,
  83. "name": name,
  84. "model": device_attr(d, 'model') or "",
  85. "manufacturer": device_attr(d, 'manufacturer') or ""
  86. } %}
  87. {% set ns.result = ns.result + [entry] %}
  88. {% endif %}
  89. {% endfor %}
  90. {{ ns.result | to_json }}
  91. `
  92. reqBody, _ := json.Marshal(HATemplateReq{Template: template})
  93. req, err := http.NewRequest("POST", url+"/api/template", bytes.NewBuffer(reqBody))
  94. if err != nil {
  95. return nil, fmt.Errorf("failed to create request: %v", err)
  96. }
  97. req.Header.Set("Authorization", "Bearer "+haConfig.Token)
  98. req.Header.Set("Content-Type", "application/json")
  99. resp, err := client.Do(req)
  100. if err != nil {
  101. return nil, fmt.Errorf("connection failed: %v", err)
  102. }
  103. defer resp.Body.Close()
  104. // Read body for better error reporting
  105. bodyBytes, err := io.ReadAll(resp.Body)
  106. if err != nil {
  107. return nil, fmt.Errorf("failed to read response body: %v", err)
  108. }
  109. if resp.StatusCode != 200 {
  110. fmt.Printf("DEBUG: HA Status Error: %s, Body: %s\n", resp.Status, string(bodyBytes))
  111. return nil, fmt.Errorf("Home Assistant returned status: %s. Body: %s", resp.Status, string(bodyBytes))
  112. }
  113. var devices []HADevice
  114. if err := json.Unmarshal(bodyBytes, &devices); err != nil {
  115. // Try to see if it's because empty result or format
  116. fmt.Printf("DEBUG: Failed to decode HA response: %s\nError: %v\n", string(bodyBytes), err)
  117. return nil, fmt.Errorf("failed to decode response: %v. Body: %s", err, string(bodyBytes))
  118. }
  119. fmt.Printf("DEBUG: Successfully fetched %d devices\n", len(devices))
  120. return devices, nil
  121. }
  122. func fetchHAEntitiesByDevice(config datatypes.JSON, deviceID string) ([]HAEntity, error) {
  123. var haConfig HAConfig
  124. b, err := config.MarshalJSON()
  125. if err != nil {
  126. return nil, fmt.Errorf("config error: %v", err)
  127. }
  128. if err := json.Unmarshal(b, &haConfig); err != nil {
  129. return nil, fmt.Errorf("invalid configuration format: %v", err)
  130. }
  131. if haConfig.URL == "" || haConfig.Token == "" {
  132. return nil, fmt.Errorf("URL and Token are required")
  133. }
  134. client := &http.Client{Timeout: 10 * time.Second}
  135. url := haConfig.URL
  136. // Robust URL handling
  137. url = strings.TrimSuffix(url, "/")
  138. url = strings.TrimSuffix(url, "/api")
  139. // Template to fetch entities for a specific device
  140. // Using strings.Replace to avoid fmt.Sprintf interpreting Jinja2 tags {% as format specifiers
  141. rawTemplate := `
  142. {% set ns = namespace(result=[]) %}
  143. {% set device_entities = device_entities('__DEVICE_ID__') %}
  144. {% for entity_id in device_entities %}
  145. {% set state = states[entity_id] %}
  146. {% if state %}
  147. {% set name = state.attributes.friendly_name %}
  148. {% if name is not defined or name is none %}
  149. {% set name = entity_id %}
  150. {% endif %}
  151. {% set entry = {
  152. "id": entity_id,
  153. "s": state.state,
  154. "n": name,
  155. "did": '__DEVICE_ID__',
  156. "dn": ''
  157. } %}
  158. {% set ns.result = ns.result + [entry] %}
  159. {% endif %}
  160. {% endfor %}
  161. {{ ns.result | to_json }}
  162. `
  163. template := strings.ReplaceAll(rawTemplate, "__DEVICE_ID__", deviceID)
  164. reqBody, _ := json.Marshal(HATemplateReq{Template: template})
  165. req, err := http.NewRequest("POST", url+"/api/template", bytes.NewBuffer(reqBody))
  166. if err != nil {
  167. return nil, fmt.Errorf("failed to create request: %v", err)
  168. }
  169. req.Header.Set("Authorization", "Bearer "+haConfig.Token)
  170. req.Header.Set("Content-Type", "application/json")
  171. resp, err := client.Do(req)
  172. if err != nil {
  173. return nil, fmt.Errorf("connection failed: %v", err)
  174. }
  175. defer resp.Body.Close()
  176. // Read body for better error reporting
  177. bodyBytes, err := io.ReadAll(resp.Body)
  178. if err != nil {
  179. return nil, fmt.Errorf("failed to read response body: %v", err)
  180. }
  181. if resp.StatusCode != 200 {
  182. fmt.Printf("DEBUG: HA Status Error (Entities): %s, Body: %s\n", resp.Status, string(bodyBytes))
  183. return nil, fmt.Errorf("Home Assistant returned status: %s. Body: %s", resp.Status, string(bodyBytes))
  184. }
  185. var tmplResults []HATemplateResult
  186. if err := json.Unmarshal(bodyBytes, &tmplResults); err != nil {
  187. fmt.Printf("DEBUG: Failed to decode HA response (Entities): %s\nError: %v\n", string(bodyBytes), err)
  188. return nil, fmt.Errorf("failed to decode response: %v. Body: %s", err, string(bodyBytes))
  189. }
  190. fmt.Printf("DEBUG: Successfully fetched %d entities for device %s\n", len(tmplResults), deviceID)
  191. entities := make([]HAEntity, len(tmplResults))
  192. for i, r := range tmplResults {
  193. entities[i] = HAEntity{
  194. EntityID: r.ID,
  195. State: r.State,
  196. Attributes: map[string]interface{}{"friendly_name": r.Name},
  197. DeviceID: r.DID,
  198. DeviceName: r.DName,
  199. }
  200. }
  201. // Sort by friendly_name
  202. sort.Slice(entities, func(i, j int) bool {
  203. nameI, okI := entities[i].Attributes["friendly_name"].(string)
  204. nameJ, okJ := entities[j].Attributes["friendly_name"].(string)
  205. if !okI {
  206. nameI = entities[i].EntityID
  207. }
  208. if !okJ {
  209. nameJ = entities[j].EntityID
  210. }
  211. return nameI < nameJ
  212. })
  213. return entities, nil
  214. }
  215. func fetchHAEntities(config datatypes.JSON) ([]HAEntity, error) {
  216. var haConfig HAConfig
  217. b, err := config.MarshalJSON()
  218. if err != nil {
  219. return nil, fmt.Errorf("config error: %v", err)
  220. }
  221. if err := json.Unmarshal(b, &haConfig); err != nil {
  222. return nil, fmt.Errorf("invalid configuration format: %v", err)
  223. }
  224. if haConfig.URL == "" || haConfig.Token == "" {
  225. return nil, fmt.Errorf("URL and Token are required")
  226. }
  227. client := &http.Client{Timeout: 10 * time.Second}
  228. url := haConfig.URL
  229. // Robust URL handling
  230. url = strings.TrimSuffix(url, "/")
  231. url = strings.TrimSuffix(url, "/api")
  232. // Try Template API first to get device info
  233. // Using namespace to avoid list.append security restriction
  234. template := `
  235. {% set ns = namespace(result=[]) %}
  236. {% for state in states %}
  237. {% set name = state.attributes.friendly_name %}
  238. {% if name is not defined or name is none %}
  239. {% set name = state.entity_id %}
  240. {% endif %}
  241. {% set d = device_id(state.entity_id) %}
  242. {% if d %}
  243. {% set d_name = device_attr(d, 'name_by_user') or device_attr(d, 'name') or 'Unknown' %}
  244. {% set entry = {
  245. "id": state.entity_id,
  246. "s": state.state,
  247. "n": name,
  248. "did": d,
  249. "dn": d_name
  250. } %}
  251. {% set ns.result = ns.result + [entry] %}
  252. {% else %}
  253. {% set entry = {
  254. "id": state.entity_id,
  255. "s": state.state,
  256. "n": name,
  257. "did": "",
  258. "dn": ""
  259. } %}
  260. {% set ns.result = ns.result + [entry] %}
  261. {% endif %}
  262. {% endfor %}
  263. {{ ns.result | to_json }}
  264. `
  265. // Clean up newlines/spaces for template req? Not strictly needed for JSON but good practice
  266. // Actually JSON marshalling handles it.
  267. reqBody, _ := json.Marshal(HATemplateReq{Template: template})
  268. req, err := http.NewRequest("POST", url+"/api/template", bytes.NewBuffer(reqBody))
  269. if err == nil {
  270. req.Header.Set("Authorization", "Bearer "+haConfig.Token)
  271. req.Header.Set("Content-Type", "application/json")
  272. resp, err := client.Do(req)
  273. if err == nil && resp.StatusCode == 200 {
  274. defer resp.Body.Close()
  275. // Parse template result
  276. // HA returns string body which IS the rendered template (JSON)
  277. // But careful: sometimes it's plain text.
  278. // "to_json" filter ensures it's JSON.
  279. var tmplResults []HATemplateResult
  280. if err := json.NewDecoder(resp.Body).Decode(&tmplResults); err == nil {
  281. // Convert to HAEntity
  282. entities := make([]HAEntity, len(tmplResults))
  283. for i, r := range tmplResults {
  284. entities[i] = HAEntity{
  285. EntityID: r.ID,
  286. State: r.State,
  287. Attributes: map[string]interface{}{"friendly_name": r.Name}, // Simplified attributes
  288. DeviceID: r.DID,
  289. DeviceName: r.DName,
  290. }
  291. }
  292. // Sort by friendly_name
  293. sort.Slice(entities, func(i, j int) bool {
  294. nameI, okI := entities[i].Attributes["friendly_name"].(string)
  295. nameJ, okJ := entities[j].Attributes["friendly_name"].(string)
  296. if !okI {
  297. nameI = entities[i].EntityID
  298. }
  299. if !okJ {
  300. nameJ = entities[j].EntityID
  301. }
  302. return nameI < nameJ
  303. })
  304. return entities, nil
  305. }
  306. // If decode failed, fallthrough to legacy method
  307. }
  308. }
  309. // Fallback to /api/states
  310. req, err = http.NewRequest("GET", url+"/api/states", nil)
  311. if err != nil {
  312. return nil, fmt.Errorf("failed to create request: %v", err)
  313. }
  314. req.Header.Set("Authorization", "Bearer "+haConfig.Token)
  315. req.Header.Set("Content-Type", "application/json")
  316. resp, err := client.Do(req)
  317. if err != nil {
  318. return nil, fmt.Errorf("connection failed: %v", err)
  319. }
  320. defer resp.Body.Close()
  321. if resp.StatusCode != 200 {
  322. return nil, fmt.Errorf("Home Assistant returned status: %s", resp.Status)
  323. }
  324. var entities []HAEntity
  325. if err := json.NewDecoder(resp.Body).Decode(&entities); err != nil {
  326. return nil, fmt.Errorf("failed to decode response: %v", err)
  327. }
  328. // Sort by friendly_name
  329. sort.Slice(entities, func(i, j int) bool {
  330. nameI, okI := entities[i].Attributes["friendly_name"].(string)
  331. nameJ, okJ := entities[j].Attributes["friendly_name"].(string)
  332. if !okI {
  333. nameI = entities[i].EntityID
  334. }
  335. if !okJ {
  336. nameJ = entities[j].EntityID
  337. }
  338. return nameI < nameJ
  339. })
  340. return entities, nil
  341. }
  342. func testHAConnection(config datatypes.JSON) (bool, string) {
  343. var haConfig HAConfig
  344. b, err := config.MarshalJSON()
  345. if err != nil {
  346. return false, "Config error"
  347. }
  348. if err := json.Unmarshal(b, &haConfig); err != nil {
  349. return false, "Invalid configuration format"
  350. }
  351. if haConfig.URL == "" || haConfig.Token == "" {
  352. return false, "URL and Token are required"
  353. }
  354. client := &http.Client{Timeout: 5 * time.Second}
  355. // Removing trailing slash if present to avoid double slash
  356. url := haConfig.URL
  357. url = strings.TrimSuffix(url, "/")
  358. url = strings.TrimSuffix(url, "/api")
  359. req, err := http.NewRequest("GET", url+"/api/", nil)
  360. if err != nil {
  361. return false, "Failed to create request: " + err.Error()
  362. }
  363. req.Header.Set("Authorization", "Bearer "+haConfig.Token)
  364. req.Header.Set("Content-Type", "application/json")
  365. resp, err := client.Do(req)
  366. if err != nil {
  367. return false, "Connection failed: " + err.Error()
  368. }
  369. defer resp.Body.Close()
  370. if resp.StatusCode == 200 {
  371. return true, "Success"
  372. }
  373. return false, "Home Assistant returned status: " + resp.Status
  374. }
  375. func GetSources(c *gin.Context) {
  376. var sources []models.IntegrationSource
  377. if err := models.DB.Find(&sources).Error; err != nil {
  378. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  379. return
  380. }
  381. c.JSON(http.StatusOK, sources)
  382. }
  383. func CreateSource(c *gin.Context) {
  384. var source models.IntegrationSource
  385. if err := c.ShouldBindJSON(&source); err != nil {
  386. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  387. return
  388. }
  389. // DEBUG LOG
  390. fmt.Printf("DEBUG: CreateSource received: %+v, Status: %s\n", source, source.Status)
  391. if err := models.DB.Create(&source).Error; err != nil {
  392. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  393. return
  394. }
  395. c.JSON(http.StatusCreated, source)
  396. }
  397. func UpdateSource(c *gin.Context) {
  398. id := c.Param("id")
  399. var source models.IntegrationSource
  400. if err := models.DB.First(&source, "id = ?", id).Error; err != nil {
  401. c.JSON(http.StatusNotFound, gin.H{"error": "Source not found"})
  402. return
  403. }
  404. if err := c.ShouldBindJSON(&source); err != nil {
  405. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  406. return
  407. }
  408. // DEBUG LOG
  409. fmt.Printf("DEBUG: UpdateSource received: %+v, Status: %s\n", source, source.Status)
  410. models.DB.Save(&source)
  411. c.JSON(http.StatusOK, source)
  412. }
  413. func DeleteSource(c *gin.Context) {
  414. id := c.Param("id")
  415. if err := models.DB.Delete(&models.IntegrationSource{}, "id = ?", id).Error; err != nil {
  416. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  417. return
  418. }
  419. c.JSON(http.StatusOK, gin.H{"message": "Source deleted"})
  420. }
  421. // TestSourceConnection 测试连接
  422. func TestSourceConnection(c *gin.Context) {
  423. var source models.IntegrationSource
  424. // 允许直接传参测试,或者传 ID 测试已有
  425. if err := c.ShouldBindJSON(&source); err != nil {
  426. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  427. return
  428. }
  429. var success bool
  430. var msg string
  431. switch source.DriverType {
  432. case "HOME_ASSISTANT":
  433. success, msg = testHAConnection(source.Config)
  434. default:
  435. // Mock others for now
  436. success = true
  437. msg = "Connection simulated (driver not implemented)"
  438. }
  439. if success {
  440. // If the source has an ID, update its status in DB
  441. if source.ID != uuid.Nil {
  442. models.DB.Model(&models.IntegrationSource{}).Where("id = ?", source.ID).Update("status", "ONLINE")
  443. }
  444. c.JSON(http.StatusOK, gin.H{"success": true, "message": "Connection successful"})
  445. } else {
  446. if source.ID != uuid.Nil {
  447. models.DB.Model(&models.IntegrationSource{}).Where("id = ?", source.ID).Update("status", "OFFLINE")
  448. }
  449. c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": msg})
  450. }
  451. }
  452. // SyncSource 同步数据
  453. func SyncSource(c *gin.Context) {
  454. id := c.Param("id")
  455. // TODO: 触发异步任务同步设备
  456. c.JSON(http.StatusOK, gin.H{"message": "Sync started for source " + id})
  457. }
  458. // GetSourceDevices 获取设备列表
  459. func GetSourceDevices(c *gin.Context) {
  460. id := c.Param("id")
  461. var source models.IntegrationSource
  462. if err := models.DB.First(&source, "id = ?", id).Error; err != nil {
  463. c.JSON(http.StatusNotFound, gin.H{"error": "Source not found"})
  464. return
  465. }
  466. if source.DriverType != "HOME_ASSISTANT" {
  467. c.JSON(http.StatusBadRequest, gin.H{"error": "Only Home Assistant sources are supported"})
  468. return
  469. }
  470. devices, err := fetchHADevices(source.Config)
  471. if err != nil {
  472. fmt.Printf("DEBUG: GetSourceDevices error: %v\n", err)
  473. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  474. return
  475. }
  476. c.JSON(http.StatusOK, devices)
  477. }
  478. // GetSourceDeviceEntities 获取指定设备的实体列表
  479. func GetSourceDeviceEntities(c *gin.Context) {
  480. id := c.Param("id")
  481. deviceID := c.Param("deviceId")
  482. var source models.IntegrationSource
  483. if err := models.DB.First(&source, "id = ?", id).Error; err != nil {
  484. c.JSON(http.StatusNotFound, gin.H{"error": "Source not found"})
  485. return
  486. }
  487. if source.DriverType != "HOME_ASSISTANT" {
  488. c.JSON(http.StatusBadRequest, gin.H{"error": "Only Home Assistant sources are supported"})
  489. return
  490. }
  491. entities, err := fetchHAEntitiesByDevice(source.Config, deviceID)
  492. if err != nil {
  493. fmt.Printf("DEBUG: GetSourceDeviceEntities error: %v\n", err)
  494. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  495. return
  496. }
  497. c.JSON(http.StatusOK, entities)
  498. }
  499. // GetSourceCandidates 获取数据源候选设备列表 (Deprecated or kept for backward compat)
  500. func GetSourceCandidates(c *gin.Context) {
  501. id := c.Param("id")
  502. var source models.IntegrationSource
  503. if err := models.DB.First(&source, "id = ?", id).Error; err != nil {
  504. c.JSON(http.StatusNotFound, gin.H{"error": "Source not found"})
  505. return
  506. }
  507. if source.DriverType != "HOME_ASSISTANT" {
  508. c.JSON(http.StatusBadRequest, gin.H{"error": "Only Home Assistant sources are supported for candidate fetching currently"})
  509. return
  510. }
  511. entities, err := fetchHAEntities(source.Config)
  512. if err != nil {
  513. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  514. return
  515. }
  516. c.JSON(http.StatusOK, entities)
  517. }
  518. // --- Device Controllers ---
  519. func GetDevices(c *gin.Context) {
  520. var devices []models.Device
  521. // Support filtering by location_id or source_id if provided
  522. locationID := c.Query("location_id")
  523. sourceID := c.Query("source_id")
  524. query := models.DB
  525. if locationID != "" {
  526. query = query.Where("location_id = ?", locationID)
  527. }
  528. if sourceID != "" {
  529. query = query.Where("source_id = ?", sourceID)
  530. }
  531. if err := query.Find(&devices).Error; err != nil {
  532. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  533. return
  534. }
  535. c.JSON(http.StatusOK, devices)
  536. }
  537. func CreateDevice(c *gin.Context) {
  538. var device models.Device
  539. if err := c.ShouldBindJSON(&device); err != nil {
  540. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  541. return
  542. }
  543. if err := models.DB.Create(&device).Error; err != nil {
  544. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  545. return
  546. }
  547. c.JSON(http.StatusCreated, device)
  548. }
  549. func UpdateDevice(c *gin.Context) {
  550. id := c.Param("id")
  551. var device models.Device
  552. if err := models.DB.First(&device, "id = ?", id).Error; err != nil {
  553. c.JSON(http.StatusNotFound, gin.H{"error": "Device not found"})
  554. return
  555. }
  556. if err := c.ShouldBindJSON(&device); err != nil {
  557. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  558. return
  559. }
  560. models.DB.Save(&device)
  561. c.JSON(http.StatusOK, device)
  562. }
  563. func DeleteDevice(c *gin.Context) {
  564. id := c.Param("id")
  565. if err := models.DB.Delete(&models.Device{}, "id = ?", id).Error; err != nil {
  566. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  567. return
  568. }
  569. c.JSON(http.StatusOK, gin.H{"message": "Device deleted"})
  570. }
  571. // --- Location Controllers ---
  572. func GetLocations(c *gin.Context) {
  573. var locations []models.SysLocation
  574. if err := models.DB.Find(&locations).Error; err != nil {
  575. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  576. return
  577. }
  578. c.JSON(http.StatusOK, locations)
  579. }
  580. func CreateLocation(c *gin.Context) {
  581. var location models.SysLocation
  582. if err := c.ShouldBindJSON(&location); err != nil {
  583. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  584. return
  585. }
  586. if err := models.DB.Create(&location).Error; err != nil {
  587. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  588. return
  589. }
  590. c.JSON(http.StatusCreated, location)
  591. }
  592. func UpdateLocation(c *gin.Context) {
  593. id := c.Param("id")
  594. var location models.SysLocation
  595. if err := models.DB.First(&location, "id = ?", id).Error; err != nil {
  596. c.JSON(http.StatusNotFound, gin.H{"error": "Location not found"})
  597. return
  598. }
  599. if err := c.ShouldBindJSON(&location); err != nil {
  600. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  601. return
  602. }
  603. models.DB.Save(&location)
  604. c.JSON(http.StatusOK, location)
  605. }
  606. func DeleteLocation(c *gin.Context) {
  607. id := c.Param("id")
  608. if err := models.DB.Delete(&models.SysLocation{}, "id = ?", id).Error; err != nil {
  609. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  610. return
  611. }
  612. c.JSON(http.StatusOK, gin.H{"message": "Location deleted"})
  613. }