EnergyFlowDiagramLinkCollection.__init__()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 2
rs 10
c 0
b 0
f 0
cc 1
nop 1
1
import uuid
2
from datetime import datetime, timedelta
3
import falcon
4
import mysql.connector
5
import simplejson as json
6
import redis
7
from core.useractivity import user_logger, admin_control, access_control, api_key_control
8
import config
9
10
11
def clear_energy_flow_diagram_cache(diagram_id=None):
12
    """
13
    Clear energy flow diagram-related cache after data modification
14
15
    Args:
16
        diagram_id: Energy flow diagram ID (optional, for specific diagram cache)
17
    """
18
    # Check if Redis is enabled
19
    if not config.redis.get('is_enabled', False):
20
        return
21
22
    redis_client = None
23
    try:
24
        redis_client = redis.Redis(
25
            host=config.redis['host'],
26
            port=config.redis['port'],
27
            password=config.redis['password'] if config.redis['password'] else None,
28
            db=config.redis['db'],
29
            decode_responses=True,
30
            socket_connect_timeout=2,
31
            socket_timeout=2
32
        )
33
        redis_client.ping()
34
35
        # Clear energy flow diagram list cache (all search query variations)
36
        list_cache_key_pattern = 'energyflowdiagram:list:*'
37
        matching_keys = redis_client.keys(list_cache_key_pattern)
38
        if matching_keys:
39
            redis_client.delete(*matching_keys)
40
41
        # Clear specific diagram cache if diagram_id is provided
42
        if diagram_id:
43
            item_cache_key = f'energyflowdiagram:item:{diagram_id}'
44
            redis_client.delete(item_cache_key)
45
            link_list_cache_key = f'energyflowdiagram:links:{diagram_id}'
46
            redis_client.delete(link_list_cache_key)
47
            node_list_cache_key = f'energyflowdiagram:nodes:{diagram_id}'
48
            redis_client.delete(node_list_cache_key)
49
            export_cache_key = f'energyflowdiagram:export:{diagram_id}'
50
            redis_client.delete(export_cache_key)
51
            # Clear link item cache
52
            link_item_cache_key_pattern = f'energyflowdiagram:link:{diagram_id}:*'
53
            matching_link_keys = redis_client.keys(link_item_cache_key_pattern)
54
            if matching_link_keys:
55
                redis_client.delete(*matching_link_keys)
56
            # Clear node item cache
57
            node_item_cache_key_pattern = f'energyflowdiagram:node:{diagram_id}:*'
58
            matching_node_keys = redis_client.keys(node_item_cache_key_pattern)
59
            if matching_node_keys:
60
                redis_client.delete(*matching_node_keys)
61
62
    except Exception:
63
        # If cache clear fails, ignore and continue
64
        pass
65
66
67
class EnergyFlowDiagramCollection:
68
    """
69
    Energy Flow Diagram Collection Resource
70
71
    This class handles CRUD operations for energy flow diagram collection.
72
    It provides endpoints for listing all energy flow diagrams and creating new ones.
73
    Energy flow diagrams represent visual representations of energy flows between
74
    different nodes in the energy management system, showing how energy moves
75
    through various components like meters, equipment, and spaces.
76
    """
77
    def __init__(self):
78
        pass
79
80
    @staticmethod
81
    def on_options(req, resp):
82
        """
83
        Handle OPTIONS request for CORS preflight
84
85
        Args:
86
            req: Falcon request object
87
            resp: Falcon response object
88
        """
89
        _ = req
90
        resp.status = falcon.HTTP_200
91
92
    @staticmethod
93
    def on_get(req, resp):
94
        """
95
        Handle GET requests to retrieve all energy flow diagrams
96
97
        Returns a list of all energy flow diagrams with their complete structure including:
98
        - Diagram ID, name, and UUID
99
        - Associated nodes (energy flow points)
100
        - Links between nodes with meter associations
101
        - Meter information (regular, offline, and virtual meters)
102
103
        Args:
104
            req: Falcon request object
105
            resp: Falcon response object
106
        """
107
        # Check authentication method (API key or session)
108
        if 'API-KEY' not in req.headers or \
109
                not isinstance(req.headers['API-KEY'], str) or \
110
                len(str.strip(req.headers['API-KEY'])) == 0:
111
            access_control(req)
112
        else:
113
            api_key_control(req)
114
115
        search_query = req.get_param('q', default=None)
116
        if search_query is not None:
117
            search_query = search_query.strip()
118
        else:
119
            search_query = ''
120
121
        # Redis cache key
122
        cache_key = f'energyflowdiagram:list:{search_query}'
123
        cache_expire = 28800  # 8 hours in seconds (long-term cache)
124
125
        # Try to get from Redis cache (only if Redis is enabled)
126
        redis_client = None
127
        if config.redis.get('is_enabled', False):
128
            try:
129
                redis_client = redis.Redis(
130
                    host=config.redis['host'],
131
                    port=config.redis['port'],
132
                    password=config.redis['password'] if config.redis['password'] else None,
133
                    db=config.redis['db'],
134
                    decode_responses=True,
135
                    socket_connect_timeout=2,
136
                    socket_timeout=2
137
                )
138
                redis_client.ping()
139
                cached_result = redis_client.get(cache_key)
140
                if cached_result:
141
                    resp.text = cached_result
142
                    return
143
            except Exception:
144
                # If Redis connection fails, continue to database query
145
                pass
146
147
        # Cache miss or Redis error - query database
148
        # Connect to database
149
        cnx = mysql.connector.connect(**config.myems_system_db)
150
        cursor = cnx.cursor()
151
152
        # Query to retrieve all regular meters for reference
153
        query = (" SELECT id, name, uuid "
154
                 " FROM tbl_meters ")
155
        cursor.execute(query)
156
        rows_meters = cursor.fetchall()
157
158
        # Build meter dictionary for quick lookup by UUID
159
        meter_dict = dict()
160
        if rows_meters is not None and len(rows_meters) > 0:
161
            for row in rows_meters:
162
                meter_dict[row[2]] = {"type": 'meter',
163
                                      "id": row[0],
164
                                      "name": row[1],
165
                                      "uuid": row[2]}
166
167
        # Query to retrieve all offline meters for reference
168
        query = (" SELECT id, name, uuid "
169
                 " FROM tbl_offline_meters ")
170
        cursor.execute(query)
171
        rows_offline_meters = cursor.fetchall()
172
173
        # Build offline meter dictionary for quick lookup by UUID
174
        offline_meter_dict = dict()
175
        if rows_offline_meters is not None and len(rows_offline_meters) > 0:
176
            for row in rows_offline_meters:
177
                offline_meter_dict[row[2]] = {"type": 'offline_meter',
178
                                              "id": row[0],
179
                                              "name": row[1],
180
                                              "uuid": row[2]}
181
182
        # Query to retrieve all virtual meters for reference
183
        query = (" SELECT id, name, uuid "
184
                 " FROM tbl_virtual_meters ")
185
        cursor.execute(query)
186
        rows_virtual_meters = cursor.fetchall()
187
188
        # Build virtual meter dictionary for quick lookup by UUID
189
        virtual_meter_dict = dict()
190
        if rows_virtual_meters is not None and len(rows_virtual_meters) > 0:
191
            for row in rows_virtual_meters:
192
                virtual_meter_dict[row[2]] = {"type": 'virtual_meter',
193
                                              "id": row[0],
194
                                              "name": row[1],
195
                                              "uuid": row[2]}
196
197
        query = (" SELECT id, energy_flow_diagram_id, name "
198
                 " FROM tbl_energy_flow_diagrams_nodes")
199
        cursor.execute(query)
200
        rows_nodes = cursor.fetchall()
201
202
        node_dict = dict()
203
        node_list_dict = dict()
204
        if rows_nodes is not None and len(rows_nodes) > 0:
205
            for row in rows_nodes:
206
                node_dict[row[0]] = row[2]
207
                if node_list_dict.get(row[1]) is None:
208
                    node_list_dict[row[1]] = list()
209
                node_list_dict[row[1]].append({"id": row[0], "name": row[2]})
210
211
        query = (" SELECT id, energy_flow_diagram_id, source_node_id, target_node_id, meter_uuid "
212
                 " FROM tbl_energy_flow_diagrams_links")
213
        cursor.execute(query)
214
        rows_links = cursor.fetchall()
215
216
        link_list_dict = dict()
217
        if rows_links is not None and len(rows_links) > 0:
218
            for row in rows_links:
219
                # find meter by uuid
220
                meter = meter_dict.get(row[4], None)
221
                if meter is None:
222
                    meter = virtual_meter_dict.get(row[4], None)
223
                if meter is None:
224
                    meter = offline_meter_dict.get(row[4], None)
225
226
                if link_list_dict.get(row[1]) is None:
227
                    link_list_dict[row[1]] = list()
228
                link_list_dict[row[1]].append({"id": row[0],
229
                                               "source_node": {
230
                                                   "id": row[2],
231
                                                   "name": node_dict.get(row[2])},
232
                                               "target_node": {
233
                                                   "id": row[3],
234
                                                   "name": node_dict.get(row[3])},
235
                                               "meter": meter})
236
237
        query = (" SELECT id, name, uuid "
238
                 " FROM tbl_energy_flow_diagrams ")
239
240
        params=[]
241
        if search_query:
242
            query += " WHERE name LIKE %s"
243
            params = [f'%{search_query}%']
244
        query +=  " ORDER BY id "
245
        cursor.execute(query,params)
246
        rows_energy_flow_diagrams = cursor.fetchall()
247
248
        result = list()
249
        if rows_energy_flow_diagrams is not None and len(rows_energy_flow_diagrams) > 0:
250
            for row in rows_energy_flow_diagrams:
251
252
                meta_result = {"id": row[0],
253
                               "name": row[1],
254
                               "uuid": row[2],
255
                               "nodes": node_list_dict.get(row[0], None),
256
                               "links": link_list_dict.get(row[0], None), }
257
                result.append(meta_result)
258
259
        cursor.close()
260
        cnx.close()
261
262
        # Store result in Redis cache
263
        result_json = json.dumps(result)
264
        if redis_client:
265
            try:
266
                redis_client.setex(cache_key, cache_expire, result_json)
267
            except Exception:
268
                # If cache store fails, ignore and continue
269
                pass
270
271
        resp.text = result_json
272
273
    @staticmethod
274
    @user_logger
275
    def on_post(req, resp):
276
        """
277
        Handle POST requests to create a new energy flow diagram
278
279
        Creates a new energy flow diagram with the provided name.
280
        The diagram will be empty initially and nodes/links can be added separately.
281
282
        Args:
283
            req: Falcon request object containing diagram data
284
            resp: Falcon response object
285
        """
286
        admin_control(req)
287
        try:
288
            raw_json = req.stream.read().decode('utf-8')
289
        except UnicodeDecodeError as ex:
290
            print("Failed to decode request")
291
            raise falcon.HTTPError(status=falcon.HTTP_400,
292
                                   title='API.BAD_REQUEST',
293
                                   description='API.INVALID_ENCODING')
294
        except Exception as ex:
295
            print("Unexpected error reading request stream")
296
            raise falcon.HTTPError(status=falcon.HTTP_400,
297
                                   title='API.BAD_REQUEST',
298
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
299
300
        new_values = json.loads(raw_json)
301
302
        if 'name' not in new_values['data'].keys() or \
303
                not isinstance(new_values['data']['name'], str) or \
304
                len(str.strip(new_values['data']['name'])) == 0:
305
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
306
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_NAME')
307
        name = str.strip(new_values['data']['name'])
308
309
        cnx = mysql.connector.connect(**config.myems_system_db)
310
        cursor = cnx.cursor()
311
312
        cursor.execute(" SELECT name "
313
                       " FROM tbl_energy_flow_diagrams "
314
                       " WHERE name = %s ", (name,))
315
        if cursor.fetchone() is not None:
316
            cursor.close()
317
            cnx.close()
318
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
319
                                   description='API.ENERGY_FLOW_DIAGRAM_NAME_IS_ALREADY_IN_USE')
320
321
        add_values = (" INSERT INTO tbl_energy_flow_diagrams "
322
                      "    (name, uuid) "
323
                      " VALUES (%s, %s) ")
324
        cursor.execute(add_values, (name,
325
                                    str(uuid.uuid4())))
326
        new_id = cursor.lastrowid
327
        cnx.commit()
328
        cursor.close()
329
        cnx.close()
330
331
        # Clear cache after creating new diagram
332
        clear_energy_flow_diagram_cache()
333
334
        resp.status = falcon.HTTP_201
335
        resp.location = '/energyflowdiagrams/' + str(new_id)
336
337
338
class EnergyFlowDiagramItem:
339
    """
340
    Energy Flow Diagram Item Resource
341
342
    This class handles CRUD operations for individual energy flow diagrams.
343
    It provides endpoints for retrieving, updating, and deleting specific
344
    energy flow diagrams by their ID.
345
    """
346
    def __init__(self):
347
        pass
348
349
    @staticmethod
350
    def on_options(req, resp, id_):
351
        _ = req
352
        resp.status = falcon.HTTP_200
353
        _ = id_
354
355 View Code Duplication
    @staticmethod
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
356
    def on_get(req, resp, id_):
357
        if 'API-KEY' not in req.headers or \
358
                not isinstance(req.headers['API-KEY'], str) or \
359
                len(str.strip(req.headers['API-KEY'])) == 0:
360
            access_control(req)
361
        else:
362
            api_key_control(req)
363
        if not id_.isdigit() or int(id_) <= 0:
364
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
365
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_ID')
366
367
        # Redis cache key
368
        cache_key = f'energyflowdiagram:item:{id_}'
369
        cache_expire = 28800  # 8 hours in seconds (long-term cache)
370
371
        # Try to get from Redis cache (only if Redis is enabled)
372
        redis_client = None
373
        if config.redis.get('is_enabled', False):
374
            try:
375
                redis_client = redis.Redis(
376
                    host=config.redis['host'],
377
                    port=config.redis['port'],
378
                    password=config.redis['password'] if config.redis['password'] else None,
379
                    db=config.redis['db'],
380
                    decode_responses=True,
381
                    socket_connect_timeout=2,
382
                    socket_timeout=2
383
                )
384
                redis_client.ping()
385
                cached_result = redis_client.get(cache_key)
386
                if cached_result:
387
                    resp.text = cached_result
388
                    return
389
            except Exception:
390
                # If Redis connection fails, continue to database query
391
                pass
392
393
        # Cache miss or Redis error - query database
394
        cnx = mysql.connector.connect(**config.myems_system_db)
395
        cursor = cnx.cursor()
396
397
        query = (" SELECT id, name, uuid "
398
                 " FROM tbl_meters ")
399
        cursor.execute(query)
400
        rows_meters = cursor.fetchall()
401
402
        meter_dict = dict()
403
        if rows_meters is not None and len(rows_meters) > 0:
404
            for row in rows_meters:
405
                meter_dict[row[2]] = {"type": 'meter',
406
                                      "id": row[0],
407
                                      "name": row[1],
408
                                      "uuid": row[2]}
409
410
        query = (" SELECT id, name, uuid "
411
                 " FROM tbl_offline_meters ")
412
        cursor.execute(query)
413
        rows_offline_meters = cursor.fetchall()
414
415
        offline_meter_dict = dict()
416
        if rows_offline_meters is not None and len(rows_offline_meters) > 0:
417
            for row in rows_offline_meters:
418
                offline_meter_dict[row[2]] = {"type": 'offline_meter',
419
                                              "id": row[0],
420
                                              "name": row[1],
421
                                              "uuid": row[2]}
422
423
        query = (" SELECT id, name, uuid "
424
                 " FROM tbl_virtual_meters ")
425
        cursor.execute(query)
426
        rows_virtual_meters = cursor.fetchall()
427
428
        virtual_meter_dict = dict()
429
        if rows_virtual_meters is not None and len(rows_virtual_meters) > 0:
430
            for row in rows_virtual_meters:
431
                virtual_meter_dict[row[2]] = {"type": 'virtual_meter',
432
                                              "id": row[0],
433
                                              "name": row[1],
434
                                              "uuid": row[2]}
435
436
        query = (" SELECT id, energy_flow_diagram_id, name "
437
                 " FROM tbl_energy_flow_diagrams_nodes")
438
        cursor.execute(query)
439
        rows_nodes = cursor.fetchall()
440
441
        node_dict = dict()
442
        node_list_dict = dict()
443
        if rows_nodes is not None and len(rows_nodes) > 0:
444
            for row in rows_nodes:
445
                node_dict[row[0]] = row[2]
446
                if node_list_dict.get(row[1]) is None:
447
                    node_list_dict[row[1]] = list()
448
                node_list_dict[row[1]].append({"id": row[0], "name": row[2]})
449
450
        query = (" SELECT id, energy_flow_diagram_id, source_node_id, target_node_id, meter_uuid "
451
                 " FROM tbl_energy_flow_diagrams_links")
452
        cursor.execute(query)
453
        rows_links = cursor.fetchall()
454
455
        link_list_dict = dict()
456
        if rows_links is not None and len(rows_links) > 0:
457
            for row in rows_links:
458
                # find meter by uuid
459
                meter = meter_dict.get(row[4], None)
460
                if meter is None:
461
                    meter = virtual_meter_dict.get(row[4], None)
462
                if meter is None:
463
                    meter = offline_meter_dict.get(row[4], None)
464
465
                if link_list_dict.get(row[1]) is None:
466
                    link_list_dict[row[1]] = list()
467
                link_list_dict[row[1]].append({"id": row[0],
468
                                               "source_node": {
469
                                                   "id": row[2],
470
                                                   "name": node_dict.get(row[2])},
471
                                               "target_node": {
472
                                                   "id": row[3],
473
                                                   "name": node_dict.get(row[3])},
474
                                               "meter": meter})
475
476
        query = (" SELECT id, name, uuid "
477
                 " FROM tbl_energy_flow_diagrams "
478
                 " WHERE id = %s ")
479
        cursor.execute(query, (id_,))
480
        row = cursor.fetchone()
481
        cursor.close()
482
        cnx.close()
483
484
        if row is None:
485
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
486
                                   description='API.ENERGY_FLOW_DIAGRAM_NOT_FOUND')
487
        else:
488
            meta_result = {"id": row[0],
489
                           "name": row[1],
490
                           "uuid": row[2],
491
                           "nodes": node_list_dict.get(row[0], None),
492
                           "links": link_list_dict.get(row[0], None),
493
                           }
494
495
        # Store result in Redis cache
496
        result_json = json.dumps(meta_result)
497
        if redis_client:
498
            try:
499
                redis_client.setex(cache_key, cache_expire, result_json)
500
            except Exception:
501
                # If cache store fails, ignore and continue
502
                pass
503
504
        resp.text = result_json
505
506
    @staticmethod
507
    @user_logger
508
    def on_delete(req, resp, id_):
509
        admin_control(req)
510
        if not id_.isdigit() or int(id_) <= 0:
511
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
512
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_ID')
513
514
        cnx = mysql.connector.connect(**config.myems_system_db)
515
        cursor = cnx.cursor()
516
517
        # check relation with spaces
518
        cursor.execute(" SELECT id "
519
                       " FROM  tbl_spaces_energy_flow_diagrams "
520
                       " WHERE energy_flow_diagram_id = %s ", (id_,))
521
        rows_spaces = cursor.fetchall()
522
        if rows_spaces is not None and len(rows_spaces) > 0:
523
            cursor.close()
524
            cnx.close()
525
            raise falcon.HTTPError(status=falcon.HTTP_400,
526
                                   title='API.BAD_REQUEST',
527
                                   description='API.THERE_IS_RELATION_WITH_SPACES')
528
529
        # delete all associated nodes
530
        cursor.execute(" DELETE FROM tbl_energy_flow_diagrams_nodes"
531
                       " WHERE energy_flow_diagram_id = %s ", (id_,))
532
        cnx.commit()
533
534
        # delete all associated links
535
        cursor.execute(" DELETE FROM tbl_energy_flow_diagrams_links"
536
                       " WHERE energy_flow_diagram_id = %s ", (id_,))
537
        cnx.commit()
538
539
        cursor.execute(" DELETE FROM tbl_energy_flow_diagrams"
540
                       " WHERE id = %s ", (id_,))
541
        cnx.commit()
542
543
        cursor.close()
544
        cnx.close()
545
546
        # Clear cache after deleting diagram
547
        clear_energy_flow_diagram_cache(diagram_id=id_)
548
549
        resp.status = falcon.HTTP_204
550
551
    @staticmethod
552
    @user_logger
553
    def on_put(req, resp, id_):
554
        """Handles PUT requests"""
555
        admin_control(req)
556
        if not id_.isdigit() or int(id_) <= 0:
557
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
558
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_ID')
559
        try:
560
            raw_json = req.stream.read().decode('utf-8')
561
        except UnicodeDecodeError as ex:
562
            print("Failed to decode request")
563
            raise falcon.HTTPError(status=falcon.HTTP_400,
564
                                   title='API.BAD_REQUEST',
565
                                   description='API.INVALID_ENCODING')
566
        except Exception as ex:
567
            print("Unexpected error reading request stream")
568
            raise falcon.HTTPError(status=falcon.HTTP_400,
569
                                   title='API.BAD_REQUEST',
570
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
571
572
        new_values = json.loads(raw_json)
573
574
        if 'name' not in new_values['data'].keys() or \
575
                not isinstance(new_values['data']['name'], str) or \
576
                len(str.strip(new_values['data']['name'])) == 0:
577
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
578
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_NAME')
579
        name = str.strip(new_values['data']['name'])
580
581
        cnx = mysql.connector.connect(**config.myems_system_db)
582
        cursor = cnx.cursor()
583
584
        cursor.execute(" SELECT name "
585
                       " FROM tbl_energy_flow_diagrams "
586
                       " WHERE id = %s ", (id_,))
587
        if cursor.fetchone() is None:
588
            cursor.close()
589
            cnx.close()
590
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
591
                                   description='API.ENERGY_FLOW_DIAGRAM_NOT_FOUND')
592
593
        cursor.execute(" SELECT name "
594
                       " FROM tbl_energy_flow_diagrams "
595
                       " WHERE name = %s AND id != %s ", (name, id_))
596
        if cursor.fetchone() is not None:
597
            cursor.close()
598
            cnx.close()
599
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
600
                                   description='API.ENERGY_FLOW_DIAGRAM_NAME_IS_ALREADY_IN_USE')
601
602
        update_row = (" UPDATE tbl_energy_flow_diagrams "
603
                      " SET name = %s "
604
                      " WHERE id = %s ")
605
        cursor.execute(update_row, (name,
606
                                    id_))
607
        cnx.commit()
608
609
        cursor.close()
610
        cnx.close()
611
612
        # Clear cache after updating diagram
613
        clear_energy_flow_diagram_cache(diagram_id=id_)
614
615
        resp.status = falcon.HTTP_200
616
617
618
class EnergyFlowDiagramLinkCollection:
619
    def __init__(self):
620
        pass
621
622
    @staticmethod
623
    def on_options(req, resp, id_):
624
        _ = req
625
        resp.status = falcon.HTTP_200
626
        _ = id_
627
628
    @staticmethod
629
    def on_get(req, resp, id_):
630
        if 'API-KEY' not in req.headers or \
631
                not isinstance(req.headers['API-KEY'], str) or \
632
                len(str.strip(req.headers['API-KEY'])) == 0:
633
            access_control(req)
634
        else:
635
            api_key_control(req)
636
        if not id_.isdigit() or int(id_) <= 0:
637
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
638
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_ID')
639
640
        # Redis cache key
641
        cache_key = f'energyflowdiagram:links:{id_}'
642
        cache_expire = 28800  # 8 hours in seconds (long-term cache)
643
644
        # Try to get from Redis cache (only if Redis is enabled)
645
        redis_client = None
646
        if config.redis.get('is_enabled', False):
647
            try:
648
                redis_client = redis.Redis(
649
                    host=config.redis['host'],
650
                    port=config.redis['port'],
651
                    password=config.redis['password'] if config.redis['password'] else None,
652
                    db=config.redis['db'],
653
                    decode_responses=True,
654
                    socket_connect_timeout=2,
655
                    socket_timeout=2
656
                )
657
                redis_client.ping()
658
                cached_result = redis_client.get(cache_key)
659
                if cached_result:
660
                    resp.text = cached_result
661
                    return
662
            except Exception:
663
                # If Redis connection fails, continue to database query
664
                pass
665
666
        # Cache miss or Redis error - query database
667
        cnx = mysql.connector.connect(**config.myems_system_db)
668
        cursor = cnx.cursor()
669
670
        query = (" SELECT id, name "
671
                 " FROM tbl_energy_flow_diagrams_nodes ")
672
        cursor.execute(query)
673
        rows_nodes = cursor.fetchall()
674
675
        node_dict = dict()
676
        if rows_nodes is not None and len(rows_nodes) > 0:
677
            for row in rows_nodes:
678
                node_dict[row[0]] = {"id": row[0],
679
                                     "name": row[1]}
680
681
        query = (" SELECT id, name, uuid "
682
                 " FROM tbl_meters ")
683
        cursor.execute(query)
684
        rows_meters = cursor.fetchall()
685
686
        meter_dict = dict()
687
        if rows_meters is not None and len(rows_meters) > 0:
688
            for row in rows_meters:
689
                meter_dict[row[2]] = {"type": 'meter',
690
                                      "id": row[0],
691
                                      "name": row[1],
692
                                      "uuid": row[2]}
693
694
        query = (" SELECT id, name, uuid "
695
                 " FROM tbl_offline_meters ")
696
        cursor.execute(query)
697
        rows_offline_meters = cursor.fetchall()
698
699
        offline_meter_dict = dict()
700
        if rows_offline_meters is not None and len(rows_offline_meters) > 0:
701
            for row in rows_offline_meters:
702
                offline_meter_dict[row[2]] = {"type": 'offline_meter',
703
                                              "id": row[0],
704
                                              "name": row[1],
705
                                              "uuid": row[2]}
706
707
        query = (" SELECT id, name, uuid "
708
                 " FROM tbl_virtual_meters ")
709
        cursor.execute(query)
710
        rows_virtual_meters = cursor.fetchall()
711
712
        virtual_meter_dict = dict()
713
        if rows_virtual_meters is not None and len(rows_virtual_meters) > 0:
714
            for row in rows_virtual_meters:
715
                virtual_meter_dict[row[2]] = {"type": 'virtual_meter',
716
                                              "id": row[0],
717
                                              "name": row[1],
718
                                              "uuid": row[2]}
719
720
        cursor.execute(" SELECT name "
721
                       " FROM tbl_energy_flow_diagrams "
722
                       " WHERE id = %s ", (id_,))
723
        if cursor.fetchone() is None:
724
            cursor.close()
725
            cnx.close()
726
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
727
                                   description='API.ENERGY_FLOW_DIAGRAM_NOT_FOUND')
728
729
        query = (" SELECT id, source_node_id, target_node_id, meter_uuid "
730
                 " FROM tbl_energy_flow_diagrams_links "
731
                 " WHERE energy_flow_diagram_id = %s "
732
                 " ORDER BY id ")
733
        cursor.execute(query, (id_, ))
734
        rows_links = cursor.fetchall()
735
736
        result = list()
737
        if rows_links is not None and len(rows_links) > 0:
738
            for row in rows_links:
739
                source_node = node_dict.get(row[1], None)
740
                target_node = node_dict.get(row[2], None)
741
                # find meter by uuid
742
                meter = meter_dict.get(row[3], None)
743
                if meter is None:
744
                    meter = virtual_meter_dict.get(row[3], None)
745
                if meter is None:
746
                    meter = offline_meter_dict.get(row[3], None)
747
748
                meta_result = {"id": row[0],
749
                               "source_node": source_node,
750
                               "target_node": target_node,
751
                               "meter": meter}
752
                result.append(meta_result)
753
754
        cursor.close()
755
        cnx.close()
756
757
        # Store result in Redis cache
758
        result_json = json.dumps(result)
759
        if redis_client:
760
            try:
761
                redis_client.setex(cache_key, cache_expire, result_json)
762
            except Exception:
763
                # If cache store fails, ignore and continue
764
                pass
765
766
        resp.text = result_json
767
768
    @staticmethod
769
    @user_logger
770
    def on_post(req, resp, id_):
771
        """Handles POST requests"""
772
        admin_control(req)
773
        if not id_.isdigit() or int(id_) <= 0:
774
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
775
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_ID')
776
        try:
777
            raw_json = req.stream.read().decode('utf-8')
778
        except UnicodeDecodeError as ex:
779
            print("Failed to decode request")
780
            raise falcon.HTTPError(status=falcon.HTTP_400,
781
                                   title='API.BAD_REQUEST',
782
                                   description='API.INVALID_ENCODING')
783
        except Exception as ex:
784
            print("Unexpected error reading request stream")
785
            raise falcon.HTTPError(status=falcon.HTTP_400,
786
                                   title='API.BAD_REQUEST',
787
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
788
789
        new_values = json.loads(raw_json)
790
791
        source_node_id = None
792
        if 'source_node_id' in new_values['data'].keys():
793
            if new_values['data']['source_node_id'] is not None and \
794
                    new_values['data']['source_node_id'] <= 0:
795
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
796
                                       description='API.INVALID_SOURCE_NODE_ID')
797
            source_node_id = new_values['data']['source_node_id']
798
799
        target_node_id = None
800
        if 'target_node_id' in new_values['data'].keys():
801
            if new_values['data']['target_node_id'] is not None and \
802
                    new_values['data']['target_node_id'] <= 0:
803
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
804
                                       description='API.INVALID_TARGET_NODE_ID')
805
            target_node_id = new_values['data']['target_node_id']
806
807
        meter_uuid = None
808
        if 'meter_uuid' in new_values['data'].keys():
809
            if new_values['data']['meter_uuid'] is not None and \
810
                    isinstance(new_values['data']['meter_uuid'], str) and \
811
                    len(str.strip(new_values['data']['meter_uuid'])) > 0:
812
                meter_uuid = str.strip(new_values['data']['meter_uuid'])
813
814
        cnx = mysql.connector.connect(**config.myems_system_db)
815
        cursor = cnx.cursor()
816
817
        cursor.execute(" SELECT name "
818
                       " FROM tbl_energy_flow_diagrams "
819
                       " WHERE id = %s ", (id_,))
820
        if cursor.fetchone() is None:
821
            cursor.close()
822
            cnx.close()
823
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.NOT_FOUND',
824
                                   description='API.ENERGY_FLOW_DIAGRAM_NOT_FOUND')
825
826
        cursor.execute(" SELECT id "
827
                       " FROM tbl_energy_flow_diagrams_links "
828
                       " WHERE energy_flow_diagram_id = %s AND "
829
                       "       source_node_id = %s AND target_node_id = %s ",
830
                       (id_, source_node_id, target_node_id,))
831
        row = cursor.fetchone()
832
        if row is not None:
833
            cursor.close()
834
            cnx.close()
835
            raise falcon.HTTPError(status=falcon.HTTP_400,
836
                                   title='API.NOT_FOUND',
837
                                   description='API.ENERGY_FLOW_DIAGRAM_LINK_IS_ALREADY_IN_USE')
838
839
        query = (" SELECT id, name "
840
                 " FROM tbl_energy_flow_diagrams_nodes "
841
                 " WHERE id = %s ")
842
        cursor.execute(query, (source_node_id,))
843
        if cursor.fetchone() is None:
844
            cursor.close()
845
            cnx.close()
846
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
847
                                   description='API.SOURCE_NODE_NOT_FOUND')
848
849
        query = (" SELECT id, name "
850
                 " FROM tbl_energy_flow_diagrams_nodes "
851
                 " WHERE id = %s ")
852
        cursor.execute(query, (target_node_id,))
853
        if cursor.fetchone() is None:
854
            cursor.close()
855
            cnx.close()
856
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
857
                                   description='API.TARGET_NODE_NOT_FOUND')
858
859
        query = (" SELECT id, name, uuid "
860
                 " FROM tbl_meters ")
861
        cursor.execute(query)
862
        rows_meters = cursor.fetchall()
863
864
        meter_dict = dict()
865
        if rows_meters is not None and len(rows_meters) > 0:
866
            for row in rows_meters:
867
                meter_dict[row[2]] = {"type": 'meter',
868
                                      "id": row[0],
869
                                      "name": row[1],
870
                                      "uuid": row[2]}
871
872
        query = (" SELECT id, name, uuid "
873
                 " FROM tbl_offline_meters ")
874
        cursor.execute(query)
875
        rows_offline_meters = cursor.fetchall()
876
877
        offline_meter_dict = dict()
878
        if rows_offline_meters is not None and len(rows_offline_meters) > 0:
879
            for row in rows_offline_meters:
880
                offline_meter_dict[row[2]] = {"type": 'offline_meter',
881
                                              "id": row[0],
882
                                              "name": row[1],
883
                                              "uuid": row[2]}
884
885
        query = (" SELECT id, name, uuid "
886
                 " FROM tbl_virtual_meters ")
887
        cursor.execute(query)
888
        rows_virtual_meters = cursor.fetchall()
889
890
        virtual_meter_dict = dict()
891
        if rows_virtual_meters is not None and len(rows_virtual_meters) > 0:
892
            for row in rows_virtual_meters:
893
                virtual_meter_dict[row[2]] = {"type": 'virtual_meter',
894
                                              "id": row[0],
895
                                              "name": row[1],
896
                                              "uuid": row[2]}
897
898
        # validate meter uuid
899
        if meter_dict.get(meter_uuid) is None and \
900
                virtual_meter_dict.get(meter_uuid) is None and \
901
                offline_meter_dict.get(meter_uuid) is None:
902
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
903
                                   description='API.INVALID_METER_UUID')
904
905
        add_values = (" INSERT INTO tbl_energy_flow_diagrams_links "
906
                      "    (energy_flow_diagram_id, source_node_id, target_node_id, meter_uuid) "
907
                      " VALUES (%s, %s, %s, %s) ")
908
        cursor.execute(add_values, (id_,
909
                                    source_node_id,
910
                                    target_node_id,
911
                                    meter_uuid))
912
        new_id = cursor.lastrowid
913
        cnx.commit()
914
        cursor.close()
915
        cnx.close()
916
917
        # Clear cache after creating new link
918
        clear_energy_flow_diagram_cache(diagram_id=id_)
919
920
        resp.status = falcon.HTTP_201
921
        resp.location = '/energyflowdiagrams/' + str(id_) + 'links/' + str(new_id)
922
923
924
class EnergyFlowDiagramLinkItem:
925
    def __init__(self):
926
        pass
927
928
    @staticmethod
929
    def on_options(req, resp, id_, lid):
930
        _ = req
931
        resp.status = falcon.HTTP_200
932
        _ = id_
933
934
    @staticmethod
935
    def on_get(req, resp, id_, lid):
936
        access_control(req)
937
        if not id_.isdigit() or int(id_) <= 0:
938
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
939
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_ID')
940
941
        if not lid.isdigit() or int(lid) <= 0:
942
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
943
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_LINK_ID')
944
945
        # Redis cache key
946
        cache_key = f'energyflowdiagram:link:{id_}:{lid}'
947
        cache_expire = 28800  # 8 hours in seconds (long-term cache)
948
949
        # Try to get from Redis cache (only if Redis is enabled)
950
        redis_client = None
951
        if config.redis.get('is_enabled', False):
952
            try:
953
                redis_client = redis.Redis(
954
                    host=config.redis['host'],
955
                    port=config.redis['port'],
956
                    password=config.redis['password'] if config.redis['password'] else None,
957
                    db=config.redis['db'],
958
                    decode_responses=True,
959
                    socket_connect_timeout=2,
960
                    socket_timeout=2
961
                )
962
                redis_client.ping()
963
                cached_result = redis_client.get(cache_key)
964
                if cached_result:
965
                    resp.text = cached_result
966
                    return
967
            except Exception:
968
                # If Redis connection fails, continue to database query
969
                pass
970
971
        # Cache miss or Redis error - query database
972
        cnx = mysql.connector.connect(**config.myems_system_db)
973
        cursor = cnx.cursor()
974
975
        query = (" SELECT id, name "
976
                 " FROM tbl_energy_flow_diagrams_nodes ")
977
        cursor.execute(query)
978
        rows_nodes = cursor.fetchall()
979
980
        node_dict = dict()
981
        if rows_nodes is not None and len(rows_nodes) > 0:
982
            for row in rows_nodes:
983
                node_dict[row[0]] = {"id": row[0],
984
                                     "name": row[1]}
985
986
        query = (" SELECT id, name, uuid "
987
                 " FROM tbl_meters ")
988
        cursor.execute(query)
989
        rows_meters = cursor.fetchall()
990
991
        meter_dict = dict()
992
        if rows_meters is not None and len(rows_meters) > 0:
993
            for row in rows_meters:
994
                meter_dict[row[2]] = {"type": 'meter',
995
                                      "id": row[0],
996
                                      "name": row[1],
997
                                      "uuid": row[2]}
998
999
        query = (" SELECT id, name, uuid "
1000
                 " FROM tbl_offline_meters ")
1001
        cursor.execute(query)
1002
        rows_offline_meters = cursor.fetchall()
1003
1004
        offline_meter_dict = dict()
1005
        if rows_offline_meters is not None and len(rows_offline_meters) > 0:
1006
            for row in rows_offline_meters:
1007
                offline_meter_dict[row[2]] = {"type": 'offline_meter',
1008
                                              "id": row[0],
1009
                                              "name": row[1],
1010
                                              "uuid": row[2]}
1011
1012
        query = (" SELECT id, name, uuid "
1013
                 " FROM tbl_virtual_meters ")
1014
        cursor.execute(query)
1015
        rows_virtual_meters = cursor.fetchall()
1016
1017
        virtual_meter_dict = dict()
1018
        if rows_virtual_meters is not None and len(rows_virtual_meters) > 0:
1019
            for row in rows_virtual_meters:
1020
                virtual_meter_dict[row[2]] = {"type": 'virtual_meter',
1021
                                              "id": row[0],
1022
                                              "name": row[1],
1023
                                              "uuid": row[2]}
1024
1025
        query = (" SELECT id, source_node_id, target_node_id, meter_uuid "
1026
                 " FROM tbl_energy_flow_diagrams_links "
1027
                 " WHERE energy_flow_diagram_id = %s AND id = %s ")
1028
        cursor.execute(query, (id_, lid))
1029
        row = cursor.fetchone()
1030
        cursor.close()
1031
        cnx.close()
1032
1033
        if row is None:
1034
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
1035
                                   description='API.ENERGY_FLOW_DIAGRAM_LINK_NOT_FOUND_OR_NOT_MATCH')
1036
        else:
1037
            source_node = node_dict.get(row[1], None)
1038
            target_node = node_dict.get(row[2], None)
1039
            # find meter by uuid
1040
            meter = meter_dict.get(row[3], None)
1041
            if meter is None:
1042
                meter = virtual_meter_dict.get(row[3], None)
1043
            if meter is None:
1044
                meter = offline_meter_dict.get(row[3], None)
1045
1046
            meta_result = {"id": row[0],
1047
                           "source_node": source_node,
1048
                           "target_node": target_node,
1049
                           "meter": meter}
1050
1051
        # Store result in Redis cache
1052
        result_json = json.dumps(meta_result)
1053
        if redis_client:
1054
            try:
1055
                redis_client.setex(cache_key, cache_expire, result_json)
1056
            except Exception:
1057
                # If cache store fails, ignore and continue
1058
                pass
1059
1060
        resp.text = result_json
1061
1062
    @staticmethod
1063
    @user_logger
1064
    def on_delete(req, resp, id_, lid):
1065
        admin_control(req)
1066
        if not id_.isdigit() or int(id_) <= 0:
1067
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1068
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_ID')
1069
1070
        if not lid.isdigit() or int(lid) <= 0:
1071
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1072
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_LINK_ID')
1073
1074
        cnx = mysql.connector.connect(**config.myems_system_db)
1075
        cursor = cnx.cursor()
1076
1077
        cursor.execute(" SELECT name "
1078
                       " FROM tbl_energy_flow_diagrams "
1079
                       " WHERE id = %s ",
1080
                       (id_,))
1081
        row = cursor.fetchone()
1082
        if row is None:
1083
            cursor.close()
1084
            cnx.close()
1085
            raise falcon.HTTPError(status=falcon.HTTP_400,
1086
                                   title='API.NOT_FOUND',
1087
                                   description='API.ENERGY_FLOW_DIAGRAM_NOT_FOUND')
1088
1089
        cursor.execute(" SELECT id "
1090
                       " FROM tbl_energy_flow_diagrams_links "
1091
                       " WHERE energy_flow_diagram_id = %s AND id = %s ",
1092
                       (id_, lid,))
1093
        row = cursor.fetchone()
1094
        if row is None:
1095
            cursor.close()
1096
            cnx.close()
1097
            raise falcon.HTTPError(status=falcon.HTTP_400,
1098
                                   title='API.NOT_FOUND',
1099
                                   description='API.ENERGY_FLOW_DIAGRAM_LINK_NOT_FOUND_OR_NOT_MATCH')
1100
1101
        cursor.execute(" DELETE FROM tbl_energy_flow_diagrams_links "
1102
                       " WHERE id = %s ", (lid, ))
1103
        cnx.commit()
1104
1105
        cursor.close()
1106
        cnx.close()
1107
1108
        # Clear cache after deleting link
1109
        clear_energy_flow_diagram_cache(diagram_id=id_)
1110
1111
        resp.status = falcon.HTTP_204
1112
1113
    @staticmethod
1114
    @user_logger
1115
    def on_put(req, resp, id_, lid):
1116
        """Handles PUT requests"""
1117
        admin_control(req)
1118
        if not id_.isdigit() or int(id_) <= 0:
1119
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1120
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_ID')
1121
1122
        if not lid.isdigit() or int(lid) <= 0:
1123
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1124
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_LINK_ID')
1125
1126
        try:
1127
            raw_json = req.stream.read().decode('utf-8')
1128
        except UnicodeDecodeError as ex:
1129
            print("Failed to decode request")
1130
            raise falcon.HTTPError(status=falcon.HTTP_400,
1131
                                   title='API.BAD_REQUEST',
1132
                                   description='API.INVALID_ENCODING')
1133
        except Exception as ex:
1134
            print("Unexpected error reading request stream")
1135
            raise falcon.HTTPError(status=falcon.HTTP_400,
1136
                                   title='API.BAD_REQUEST',
1137
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
1138
1139
        new_values = json.loads(raw_json)
1140
1141
        source_node_id = None
1142
        if 'source_node_id' in new_values['data'].keys():
1143
            if new_values['data']['source_node_id'] is not None and \
1144
                    new_values['data']['source_node_id'] <= 0:
1145
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1146
                                       description='API.INVALID_SOURCE_NODE_ID')
1147
            source_node_id = new_values['data']['source_node_id']
1148
1149
        target_node_id = None
1150
        if 'target_node_id' in new_values['data'].keys():
1151
            if new_values['data']['target_node_id'] is not None and \
1152
                    new_values['data']['target_node_id'] <= 0:
1153
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1154
                                       description='API.INVALID_TARGET_NODE_ID')
1155
            target_node_id = new_values['data']['target_node_id']
1156
1157
        meter_uuid = None
1158
        if 'meter_uuid' in new_values['data'].keys():
1159
            if new_values['data']['meter_uuid'] is not None and \
1160
                    isinstance(new_values['data']['meter_uuid'], str) and \
1161
                    len(str.strip(new_values['data']['meter_uuid'])) > 0:
1162
                meter_uuid = str.strip(new_values['data']['meter_uuid'])
1163
1164
        cnx = mysql.connector.connect(**config.myems_system_db)
1165
        cursor = cnx.cursor()
1166
1167
        cursor.execute(" SELECT name "
1168
                       " FROM tbl_energy_flow_diagrams "
1169
                       " WHERE id = %s ", (id_,))
1170
        if cursor.fetchone() is None:
1171
            cursor.close()
1172
            cnx.close()
1173
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.NOT_FOUND',
1174
                                   description='API.ENERGY_FLOW_DIAGRAM_NOT_FOUND')
1175
1176
        cursor.execute(" SELECT id "
1177
                       " FROM tbl_energy_flow_diagrams_links "
1178
                       " WHERE energy_flow_diagram_id = %s AND id = %s ",
1179
                       (id_, lid,))
1180
        row = cursor.fetchone()
1181
        if row is None:
1182
            cursor.close()
1183
            cnx.close()
1184
            raise falcon.HTTPError(status=falcon.HTTP_400,
1185
                                   title='API.NOT_FOUND',
1186
                                   description='API.ENERGY_FLOW_DIAGRAM_LINK_NOT_FOUND_OR_NOT_MATCH')
1187
1188
        cursor.execute(" SELECT id "
1189
                       " FROM tbl_energy_flow_diagrams_links "
1190
                       " WHERE energy_flow_diagram_id = %s AND id != %s "
1191
                       "       AND source_node_id = %s AND target_node_id = %s ",
1192
                       (id_, lid, source_node_id, target_node_id,))
1193
        row = cursor.fetchone()
1194
        if row is not None:
1195
            cursor.close()
1196
            cnx.close()
1197
            raise falcon.HTTPError(status=falcon.HTTP_400,
1198
                                   title='API.NOT_FOUND',
1199
                                   description='API.ENERGY_FLOW_DIAGRAM_LINK_IS_ALREADY_IN_USE')
1200
1201
        query = (" SELECT id, name "
1202
                 " FROM tbl_energy_flow_diagrams_nodes "
1203
                 " WHERE id = %s ")
1204
        cursor.execute(query, (source_node_id,))
1205
        if cursor.fetchone() is None:
1206
            cursor.close()
1207
            cnx.close()
1208
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1209
                                   description='API.SOURCE_NODE_NOT_FOUND')
1210
1211
        query = (" SELECT id, name "
1212
                 " FROM tbl_energy_flow_diagrams_nodes "
1213
                 " WHERE id = %s ")
1214
        cursor.execute(query, (target_node_id,))
1215
        if cursor.fetchone() is None:
1216
            cursor.close()
1217
            cnx.close()
1218
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1219
                                   description='API.TARGET_NODE_NOT_FOUND')
1220
1221
        query = (" SELECT id, name, uuid "
1222
                 " FROM tbl_meters ")
1223
        cursor.execute(query)
1224
        rows_meters = cursor.fetchall()
1225
1226
        meter_dict = dict()
1227
        if rows_meters is not None and len(rows_meters) > 0:
1228
            for row in rows_meters:
1229
                meter_dict[row[2]] = {"type": 'meter',
1230
                                      "id": row[0],
1231
                                      "name": row[1],
1232
                                      "uuid": row[2]}
1233
1234
        query = (" SELECT id, name, uuid "
1235
                 " FROM tbl_offline_meters ")
1236
        cursor.execute(query)
1237
        rows_offline_meters = cursor.fetchall()
1238
1239
        offline_meter_dict = dict()
1240
        if rows_offline_meters is not None and len(rows_offline_meters) > 0:
1241
            for row in rows_offline_meters:
1242
                offline_meter_dict[row[2]] = {"type": 'offline_meter',
1243
                                              "id": row[0],
1244
                                              "name": row[1],
1245
                                              "uuid": row[2]}
1246
1247
        query = (" SELECT id, name, uuid "
1248
                 " FROM tbl_virtual_meters ")
1249
        cursor.execute(query)
1250
        rows_virtual_meters = cursor.fetchall()
1251
1252
        virtual_meter_dict = dict()
1253
        if rows_virtual_meters is not None and len(rows_virtual_meters) > 0:
1254
            for row in rows_virtual_meters:
1255
                virtual_meter_dict[row[2]] = {"type": 'virtual_meter',
1256
                                              "id": row[0],
1257
                                              "name": row[1],
1258
                                              "uuid": row[2]}
1259
1260
        # validate meter uuid
1261
        if meter_dict.get(meter_uuid) is None and \
1262
                virtual_meter_dict.get(meter_uuid) is None and \
1263
                offline_meter_dict.get(meter_uuid) is None:
1264
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1265
                                   description='API.INVALID_METER_UUID')
1266
1267
        add_values = (" UPDATE tbl_energy_flow_diagrams_links "
1268
                      " SET source_node_id = %s, target_node_id = %s, meter_uuid = %s "
1269
                      " WHERE id = %s ")
1270
        cursor.execute(add_values, (source_node_id,
1271
                                    target_node_id,
1272
                                    meter_uuid,
1273
                                    lid))
1274
        cnx.commit()
1275
1276
        cursor.close()
1277
        cnx.close()
1278
1279
        # Clear cache after updating link
1280
        clear_energy_flow_diagram_cache(diagram_id=id_)
1281
1282
        resp.status = falcon.HTTP_200
1283
1284
1285
class EnergyFlowDiagramNodeCollection:
1286
    def __init__(self):
1287
        pass
1288
1289
    @staticmethod
1290
    def on_options(req, resp, id_):
1291
        _ = req
1292
        resp.status = falcon.HTTP_200
1293
        _ = id_
1294
1295
    @staticmethod
1296
    def on_get(req, resp, id_):
1297
        if 'API-KEY' not in req.headers or \
1298
                not isinstance(req.headers['API-KEY'], str) or \
1299
                len(str.strip(req.headers['API-KEY'])) == 0:
1300
            access_control(req)
1301
        else:
1302
            api_key_control(req)
1303
        if not id_.isdigit() or int(id_) <= 0:
1304
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1305
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_ID')
1306
1307
        # Redis cache key
1308
        cache_key = f'energyflowdiagram:nodes:{id_}'
1309
        cache_expire = 28800  # 8 hours in seconds (long-term cache)
1310
1311
        # Try to get from Redis cache (only if Redis is enabled)
1312
        redis_client = None
1313
        if config.redis.get('is_enabled', False):
1314
            try:
1315
                redis_client = redis.Redis(
1316
                    host=config.redis['host'],
1317
                    port=config.redis['port'],
1318
                    password=config.redis['password'] if config.redis['password'] else None,
1319
                    db=config.redis['db'],
1320
                    decode_responses=True,
1321
                    socket_connect_timeout=2,
1322
                    socket_timeout=2
1323
                )
1324
                redis_client.ping()
1325
                cached_result = redis_client.get(cache_key)
1326
                if cached_result:
1327
                    resp.text = cached_result
1328
                    return
1329
            except Exception:
1330
                # If Redis connection fails, continue to database query
1331
                pass
1332
1333
        # Cache miss or Redis error - query database
1334
        cnx = mysql.connector.connect(**config.myems_system_db)
1335
        cursor = cnx.cursor()
1336
1337
        cursor.execute(" SELECT name "
1338
                       " FROM tbl_energy_flow_diagrams "
1339
                       " WHERE id = %s ", (id_,))
1340
        if cursor.fetchone() is None:
1341
            cursor.close()
1342
            cnx.close()
1343
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
1344
                                   description='API.ENERGY_FLOW_DIAGRAM_NOT_FOUND')
1345
1346
        query = (" SELECT id, name "
1347
                 " FROM tbl_energy_flow_diagrams_nodes "
1348
                 " WHERE energy_flow_diagram_id = %s "
1349
                 " ORDER BY id ")
1350
        cursor.execute(query, (id_, ))
1351
        rows_nodes = cursor.fetchall()
1352
1353
        result = list()
1354
        if rows_nodes is not None and len(rows_nodes) > 0:
1355
            for row in rows_nodes:
1356
                meta_result = {"id": row[0],
1357
                               "name": row[1]}
1358
                result.append(meta_result)
1359
1360
        cursor.close()
1361
        cnx.close()
1362
1363
        # Store result in Redis cache
1364
        result_json = json.dumps(result)
1365
        if redis_client:
1366
            try:
1367
                redis_client.setex(cache_key, cache_expire, result_json)
1368
            except Exception:
1369
                # If cache store fails, ignore and continue
1370
                pass
1371
1372
        resp.text = result_json
1373
1374
    @staticmethod
1375
    @user_logger
1376
    def on_post(req, resp, id_):
1377
        """Handles POST requests"""
1378
        admin_control(req)
1379
        if not id_.isdigit() or int(id_) <= 0:
1380
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1381
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_ID')
1382
        try:
1383
            raw_json = req.stream.read().decode('utf-8')
1384
        except UnicodeDecodeError as ex:
1385
            print("Failed to decode request")
1386
            raise falcon.HTTPError(status=falcon.HTTP_400,
1387
                                   title='API.BAD_REQUEST',
1388
                                   description='API.INVALID_ENCODING')
1389
        except Exception as ex:
1390
            print("Unexpected error reading request stream")
1391
            raise falcon.HTTPError(status=falcon.HTTP_400,
1392
                                   title='API.BAD_REQUEST',
1393
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
1394
1395
        new_values = json.loads(raw_json)
1396
1397
        if 'name' not in new_values['data'].keys() or \
1398
                not isinstance(new_values['data']['name'], str) or \
1399
                len(str.strip(new_values['data']['name'])) == 0:
1400
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1401
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_NODE_NAME')
1402
        name = str.strip(new_values['data']['name'])
1403
1404
        cnx = mysql.connector.connect(**config.myems_system_db)
1405
        cursor = cnx.cursor()
1406
1407
        cursor.execute(" SELECT name "
1408
                       " FROM tbl_energy_flow_diagrams "
1409
                       " WHERE id = %s ", (id_,))
1410
        if cursor.fetchone() is None:
1411
            cursor.close()
1412
            cnx.close()
1413
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.NOT_FOUND',
1414
                                   description='API.ENERGY_FLOW_DIAGRAM_NOT_FOUND')
1415
1416
        cursor.execute(" SELECT name "
1417
                       " FROM tbl_energy_flow_diagrams_nodes "
1418
                       " WHERE name = %s AND energy_flow_diagram_id = %s ", (name, id_))
1419
        if cursor.fetchone() is not None:
1420
            cursor.close()
1421
            cnx.close()
1422
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1423
                                   description='API.ENERGY_FLOW_DIAGRAM_NAME_IS_ALREADY_IN_USE')
1424
1425
        add_values = (" INSERT INTO tbl_energy_flow_diagrams_nodes "
1426
                      "    (energy_flow_diagram_id, name) "
1427
                      " VALUES (%s, %s) ")
1428
        cursor.execute(add_values, (id_,
1429
                                    name))
1430
        new_id = cursor.lastrowid
1431
        cnx.commit()
1432
        cursor.close()
1433
        cnx.close()
1434
1435
        # Clear cache after creating new node
1436
        clear_energy_flow_diagram_cache(diagram_id=id_)
1437
1438
        resp.status = falcon.HTTP_201
1439
        resp.location = '/energyflowdiagrams/' + str(id_) + 'nodes/' + str(new_id)
1440
1441
1442
class EnergyFlowDiagramNodeItem:
1443
    def __init__(self):
1444
        pass
1445
1446
    @staticmethod
1447
    def on_options(req, resp, id_, nid):
1448
        _ = req
1449
        resp.status = falcon.HTTP_200
1450
        _ = id_
1451
1452
    @staticmethod
1453
    def on_get(req, resp, id_, nid):
1454
        access_control(req)
1455
        if not id_.isdigit() or int(id_) <= 0:
1456
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1457
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_ID')
1458
1459
        if not nid.isdigit() or int(nid) <= 0:
1460
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1461
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_NODE_ID')
1462
1463
        # Redis cache key
1464
        cache_key = f'energyflowdiagram:node:{id_}:{nid}'
1465
        cache_expire = 28800  # 8 hours in seconds (long-term cache)
1466
1467
        # Try to get from Redis cache (only if Redis is enabled)
1468
        redis_client = None
1469
        if config.redis.get('is_enabled', False):
1470
            try:
1471
                redis_client = redis.Redis(
1472
                    host=config.redis['host'],
1473
                    port=config.redis['port'],
1474
                    password=config.redis['password'] if config.redis['password'] else None,
1475
                    db=config.redis['db'],
1476
                    decode_responses=True,
1477
                    socket_connect_timeout=2,
1478
                    socket_timeout=2
1479
                )
1480
                redis_client.ping()
1481
                cached_result = redis_client.get(cache_key)
1482
                if cached_result:
1483
                    resp.text = cached_result
1484
                    return
1485
            except Exception:
1486
                # If Redis connection fails, continue to database query
1487
                pass
1488
1489
        # Cache miss or Redis error - query database
1490
        cnx = mysql.connector.connect(**config.myems_system_db)
1491
        cursor = cnx.cursor()
1492
1493
        query = (" SELECT id, name "
1494
                 " FROM tbl_energy_flow_diagrams_nodes "
1495
                 " WHERE energy_flow_diagram_id = %s AND id = %s ")
1496
        cursor.execute(query, (id_, nid))
1497
        row = cursor.fetchone()
1498
        cursor.close()
1499
        cnx.close()
1500
1501
        if row is None:
1502
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
1503
                                   description='API.ENERGY_FLOW_DIAGRAM_NODE_NOT_FOUND_OR_NOT_MATCH')
1504
        else:
1505
            meta_result = {"id": row[0],
1506
                           "name": row[1]}
1507
1508
        # Store result in Redis cache
1509
        result_json = json.dumps(meta_result)
1510
        if redis_client:
1511
            try:
1512
                redis_client.setex(cache_key, cache_expire, result_json)
1513
            except Exception:
1514
                # If cache store fails, ignore and continue
1515
                pass
1516
1517
        resp.text = result_json
1518
1519
    @staticmethod
1520
    @user_logger
1521
    def on_delete(req, resp, id_, nid):
1522
        admin_control(req)
1523
        if not id_.isdigit() or int(id_) <= 0:
1524
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1525
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_ID')
1526
1527
        if not nid.isdigit() or int(nid) <= 0:
1528
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1529
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_NODE_ID')
1530
1531
        cnx = mysql.connector.connect(**config.myems_system_db)
1532
        cursor = cnx.cursor()
1533
1534
        cursor.execute(" SELECT name "
1535
                       " FROM tbl_energy_flow_diagrams "
1536
                       " WHERE id = %s ",
1537
                       (id_,))
1538
        row = cursor.fetchone()
1539
        if row is None:
1540
            cursor.close()
1541
            cnx.close()
1542
            raise falcon.HTTPError(status=falcon.HTTP_400,
1543
                                   title='API.NOT_FOUND',
1544
                                   description='API.ENERGY_FLOW_DIAGRAM_NOT_FOUND')
1545
1546
        cursor.execute(" SELECT name "
1547
                       " FROM tbl_energy_flow_diagrams_nodes "
1548
                       " WHERE energy_flow_diagram_id = %s AND id = %s ",
1549
                       (id_, nid,))
1550
        row = cursor.fetchone()
1551
        if row is None:
1552
            cursor.close()
1553
            cnx.close()
1554
            raise falcon.HTTPError(status=falcon.HTTP_400,
1555
                                   title='API.NOT_FOUND',
1556
                                   description='API.ENERGY_FLOW_DIAGRAM_NODE_NOT_FOUND_OR_NOT_MATCH')
1557
1558
        # check relation with links
1559
        cursor.execute(" SELECT id "
1560
                       " FROM tbl_energy_flow_diagrams_links "
1561
                       " WHERE energy_flow_diagram_id = %s AND "
1562
                       " (source_node_id = %s OR target_node_id = %s) ", (id_, nid, nid))
1563
        rows_links = cursor.fetchall()
1564
        if rows_links is not None and len(rows_links) > 0:
1565
            cursor.close()
1566
            cnx.close()
1567
            raise falcon.HTTPError(status=falcon.HTTP_400,
1568
                                   title='API.BAD_REQUEST',
1569
                                   description='API.THERE_IS_RELATION_WITH_LINKS')
1570
1571
        cursor.execute(" DELETE FROM tbl_energy_flow_diagrams_nodes "
1572
                       " WHERE id = %s ", (nid, ))
1573
        cnx.commit()
1574
1575
        cursor.close()
1576
        cnx.close()
1577
1578
        # Clear cache after deleting node
1579
        clear_energy_flow_diagram_cache(diagram_id=id_)
1580
1581
        resp.status = falcon.HTTP_204
1582
1583
    @staticmethod
1584
    @user_logger
1585
    def on_put(req, resp, id_, nid):
1586
        """Handles PUT requests"""
1587
        admin_control(req)
1588
        if not id_.isdigit() or int(id_) <= 0:
1589
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1590
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_ID')
1591
1592
        if not nid.isdigit() or int(nid) <= 0:
1593
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1594
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_NODE_ID')
1595
1596
        try:
1597
            raw_json = req.stream.read().decode('utf-8')
1598
        except UnicodeDecodeError as ex:
1599
            print("Failed to decode request")
1600
            raise falcon.HTTPError(status=falcon.HTTP_400,
1601
                                   title='API.BAD_REQUEST',
1602
                                   description='API.INVALID_ENCODING')
1603
        except Exception as ex:
1604
            print("Unexpected error reading request stream")
1605
            raise falcon.HTTPError(status=falcon.HTTP_400,
1606
                                   title='API.BAD_REQUEST',
1607
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
1608
1609
        new_values = json.loads(raw_json)
1610
1611
        if 'name' not in new_values['data'].keys() or \
1612
                not isinstance(new_values['data']['name'], str) or \
1613
                len(str.strip(new_values['data']['name'])) == 0:
1614
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1615
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_NODE_NAME')
1616
        name = str.strip(new_values['data']['name'])
1617
1618
        cnx = mysql.connector.connect(**config.myems_system_db)
1619
        cursor = cnx.cursor()
1620
1621
        cursor.execute(" SELECT name "
1622
                       " FROM tbl_energy_flow_diagrams "
1623
                       " WHERE id = %s ", (id_,))
1624
        if cursor.fetchone() is None:
1625
            cursor.close()
1626
            cnx.close()
1627
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.NOT_FOUND',
1628
                                   description='API.ENERGY_FLOW_DIAGRAM_NOT_FOUND')
1629
1630
        cursor.execute(" SELECT name "
1631
                       " FROM tbl_energy_flow_diagrams_nodes "
1632
                       " WHERE energy_flow_diagram_id = %s AND id = %s ",
1633
                       (id_, nid,))
1634
        row = cursor.fetchone()
1635
        if row is None:
1636
            cursor.close()
1637
            cnx.close()
1638
            raise falcon.HTTPError(status=falcon.HTTP_400,
1639
                                   title='API.NOT_FOUND',
1640
                                   description='API.ENERGY_FLOW_DIAGRAM_NODE_NOT_FOUND_OR_NOT_MATCH')
1641
1642
        cursor.execute(" SELECT name "
1643
                       " FROM tbl_energy_flow_diagrams_nodes "
1644
                       " WHERE name = %s AND energy_flow_diagram_id = %s  AND id != %s ", (name, id_, nid))
1645
        row = cursor.fetchone()
1646
        if row is not None:
1647
            cursor.close()
1648
            cnx.close()
1649
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1650
                                   description='API.ENERGY_FLOW_DIAGRAM_NODE_NAME_IS_ALREADY_IN_USE')
1651
1652
        add_values = (" UPDATE tbl_energy_flow_diagrams_nodes "
1653
                      " SET name = %s "
1654
                      " WHERE id = %s ")
1655
        cursor.execute(add_values, (name,
1656
                                    nid))
1657
        cnx.commit()
1658
1659
        cursor.close()
1660
        cnx.close()
1661
1662
        # Clear cache after updating node
1663
        clear_energy_flow_diagram_cache(diagram_id=id_)
1664
1665
        resp.status = falcon.HTTP_200
1666
1667
1668
class EnergyFlowDiagramExport:
1669
    def __init__(self):
1670
        pass
1671
1672
    @staticmethod
1673
    def on_options(req, resp, id_):
1674
        _ = req
1675
        resp.status = falcon.HTTP_200
1676
        _ = id_
1677
1678 View Code Duplication
    @staticmethod
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1679
    def on_get(req, resp, id_):
1680
        if 'API-KEY' not in req.headers or \
1681
                not isinstance(req.headers['API-KEY'], str) or \
1682
                len(str.strip(req.headers['API-KEY'])) == 0:
1683
            access_control(req)
1684
        else:
1685
            api_key_control(req)
1686
        if not id_.isdigit() or int(id_) <= 0:
1687
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1688
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_ID')
1689
1690
        # Redis cache key
1691
        cache_key = f'energyflowdiagram:export:{id_}'
1692
        cache_expire = 28800  # 8 hours in seconds (long-term cache)
1693
1694
        # Try to get from Redis cache (only if Redis is enabled)
1695
        redis_client = None
1696
        if config.redis.get('is_enabled', False):
1697
            try:
1698
                redis_client = redis.Redis(
1699
                    host=config.redis['host'],
1700
                    port=config.redis['port'],
1701
                    password=config.redis['password'] if config.redis['password'] else None,
1702
                    db=config.redis['db'],
1703
                    decode_responses=True,
1704
                    socket_connect_timeout=2,
1705
                    socket_timeout=2
1706
                )
1707
                redis_client.ping()
1708
                cached_result = redis_client.get(cache_key)
1709
                if cached_result:
1710
                    resp.text = cached_result
1711
                    return
1712
            except Exception:
1713
                # If Redis connection fails, continue to database query
1714
                pass
1715
1716
        # Cache miss or Redis error - query database
1717
        cnx = mysql.connector.connect(**config.myems_system_db)
1718
        cursor = cnx.cursor()
1719
1720
        query = (" SELECT id, name, uuid "
1721
                 " FROM tbl_meters ")
1722
        cursor.execute(query)
1723
        rows_meters = cursor.fetchall()
1724
1725
        meter_dict = dict()
1726
        if rows_meters is not None and len(rows_meters) > 0:
1727
            for row in rows_meters:
1728
                meter_dict[row[2]] = {"type": 'meter',
1729
                                      "id": row[0],
1730
                                      "name": row[1],
1731
                                      "uuid": row[2]}
1732
1733
        query = (" SELECT id, name, uuid "
1734
                 " FROM tbl_offline_meters ")
1735
        cursor.execute(query)
1736
        rows_offline_meters = cursor.fetchall()
1737
1738
        offline_meter_dict = dict()
1739
        if rows_offline_meters is not None and len(rows_offline_meters) > 0:
1740
            for row in rows_offline_meters:
1741
                offline_meter_dict[row[2]] = {"type": 'offline_meter',
1742
                                              "id": row[0],
1743
                                              "name": row[1],
1744
                                              "uuid": row[2]}
1745
1746
        query = (" SELECT id, name, uuid "
1747
                 " FROM tbl_virtual_meters ")
1748
        cursor.execute(query)
1749
        rows_virtual_meters = cursor.fetchall()
1750
1751
        virtual_meter_dict = dict()
1752
        if rows_virtual_meters is not None and len(rows_virtual_meters) > 0:
1753
            for row in rows_virtual_meters:
1754
                virtual_meter_dict[row[2]] = {"type": 'virtual_meter',
1755
                                              "id": row[0],
1756
                                              "name": row[1],
1757
                                              "uuid": row[2]}
1758
1759
        query = (" SELECT id, energy_flow_diagram_id, name "
1760
                 " FROM tbl_energy_flow_diagrams_nodes")
1761
        cursor.execute(query)
1762
        rows_nodes = cursor.fetchall()
1763
1764
        node_dict = dict()
1765
        node_list_dict = dict()
1766
        if rows_nodes is not None and len(rows_nodes) > 0:
1767
            for row in rows_nodes:
1768
                node_dict[row[0]] = row[2]
1769
                if node_list_dict.get(row[1]) is None:
1770
                    node_list_dict[row[1]] = list()
1771
                node_list_dict[row[1]].append({"id": row[0], "name": row[2]})
1772
1773
        query = (" SELECT id, energy_flow_diagram_id, source_node_id, target_node_id, meter_uuid "
1774
                 " FROM tbl_energy_flow_diagrams_links")
1775
        cursor.execute(query)
1776
        rows_links = cursor.fetchall()
1777
1778
        link_list_dict = dict()
1779
        if rows_links is not None and len(rows_links) > 0:
1780
            for row in rows_links:
1781
                # find meter by uuid
1782
                meter = meter_dict.get(row[4], None)
1783
                if meter is None:
1784
                    meter = virtual_meter_dict.get(row[4], None)
1785
                if meter is None:
1786
                    meter = offline_meter_dict.get(row[4], None)
1787
1788
                if link_list_dict.get(row[1]) is None:
1789
                    link_list_dict[row[1]] = list()
1790
                link_list_dict[row[1]].append({"id": row[0],
1791
                                               "source_node": {
1792
                                                   "id": row[2],
1793
                                                   "name": node_dict.get(row[2])},
1794
                                               "target_node": {
1795
                                                   "id": row[3],
1796
                                                   "name": node_dict.get(row[3])},
1797
                                               "meter": meter})
1798
1799
        query = (" SELECT id, name, uuid "
1800
                 " FROM tbl_energy_flow_diagrams "
1801
                 " WHERE id = %s ")
1802
        cursor.execute(query, (id_,))
1803
        row = cursor.fetchone()
1804
        cursor.close()
1805
        cnx.close()
1806
1807
        if row is None:
1808
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
1809
                                   description='API.ENERGY_FLOW_DIAGRAM_NOT_FOUND')
1810
        else:
1811
            meta_result = {"id": row[0],
1812
                           "name": row[1],
1813
                           "uuid": row[2],
1814
                           "nodes": node_list_dict.get(row[0], None),
1815
                           "links": link_list_dict.get(row[0], None),
1816
                           }
1817
1818
        # Store result in Redis cache
1819
        result_json = json.dumps(meta_result)
1820
        if redis_client:
1821
            try:
1822
                redis_client.setex(cache_key, cache_expire, result_json)
1823
            except Exception:
1824
                # If cache store fails, ignore and continue
1825
                pass
1826
1827
        resp.text = result_json
1828
1829
1830
class EnergyFlowDiagramImport:
1831
    def __init__(self):
1832
        pass
1833
1834
    @staticmethod
1835
    def on_options(req, resp):
1836
        _ = req
1837
        resp.status = falcon.HTTP_200
1838
1839
    @staticmethod
1840
    @user_logger
1841
    def on_post(req, resp):
1842
        """Handles POST requests"""
1843
        admin_control(req)
1844
        try:
1845
            raw_json = req.stream.read().decode('utf-8')
1846
        except UnicodeDecodeError as ex:
1847
            print("Failed to decode request")
1848
            raise falcon.HTTPError(status=falcon.HTTP_400,
1849
                                   title='API.BAD_REQUEST',
1850
                                   description='API.INVALID_ENCODING')
1851
        except Exception as ex:
1852
            print("Unexpected error reading request stream")
1853
            raise falcon.HTTPError(status=falcon.HTTP_400,
1854
                                   title='API.BAD_REQUEST',
1855
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
1856
1857
        new_values = json.loads(raw_json)
1858
1859
        if 'name' not in new_values.keys() or \
1860
                not isinstance(new_values['name'], str) or \
1861
                len(str.strip(new_values['name'])) == 0:
1862
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1863
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_NAME')
1864
        name = str.strip(new_values['name'])
1865
1866
        cnx = mysql.connector.connect(**config.myems_system_db)
1867
        cursor = cnx.cursor()
1868
1869
        cursor.execute(" SELECT name "
1870
                       " FROM tbl_energy_flow_diagrams "
1871
                       " WHERE name = %s ", (name,))
1872
        if cursor.fetchone() is not None:
1873
            cursor.close()
1874
            cnx.close()
1875
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1876
                                   description='API.ENERGY_FLOW_DIAGRAM_NAME_IS_ALREADY_IN_USE')
1877
1878
        add_values = (" INSERT INTO tbl_energy_flow_diagrams "
1879
                      "    (name, uuid) "
1880
                      " VALUES (%s, %s) ")
1881
        cursor.execute(add_values, (name,
1882
                                    str(uuid.uuid4())))
1883
        new_id = cursor.lastrowid
1884
        for node in new_values['nodes']:
1885
            if 'name' not in node.keys() or \
1886
                    not isinstance(node['name'], str) or \
1887
                    len(str.strip(node['name'])) == 0:
1888
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1889
                                       description='API.INVALID_ENERGY_FLOW_DIAGRAM_NODE_NAME')
1890
            name = str.strip(node['name'])
1891
1892
            cursor.execute(" SELECT name "
1893
                           " FROM tbl_energy_flow_diagrams "
1894
                           " WHERE id = %s ", (new_id,))
1895
            if cursor.fetchone() is None:
1896
                cursor.close()
1897
                cnx.close()
1898
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.NOT_FOUND',
1899
                                       description='API.ENERGY_FLOW_DIAGRAM_NOT_FOUND')
1900
1901
            cursor.execute(" SELECT name "
1902
                           " FROM tbl_energy_flow_diagrams_nodes "
1903
                           " WHERE name = %s AND energy_flow_diagram_id = %s ", (name, new_id))
1904
            if cursor.fetchone() is not None:
1905
                cursor.close()
1906
                cnx.close()
1907
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1908
                                       description='API.ENERGY_FLOW_DIAGRAM_NAME_IS_ALREADY_IN_USE')
1909
1910
            add_values = (" INSERT INTO tbl_energy_flow_diagrams_nodes "
1911
                          "    (energy_flow_diagram_id, name) "
1912
                          " VALUES (%s, %s) ")
1913
            cursor.execute(add_values, (new_id,
1914
                                        name))
1915
        for link in new_values['links']:
1916
            source_node_id = None
1917
            if 'id' in link['source_node'].keys():
1918
                if link['source_node']['id'] is not None and \
1919
                        link['source_node']['id'] <= 0:
1920
                    raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1921
                                           description='API.INVALID_SOURCE_NODE_ID')
1922
                source_node_id = link['source_node']['id']
1923
1924
            target_node_id = None
1925
            if 'id' in link['target_node'].keys():
1926
                if link['target_node']['id'] is not None and \
1927
                        link['target_node']['id'] <= 0:
1928
                    raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1929
                                           description='API.INVALID_TARGET_NODE_ID')
1930
                target_node_id = link['target_node']['id']
1931
1932
            meter_uuid = None
1933
            if 'uuid' in link['meter'].keys():
1934
                if link['meter']['uuid'] is not None and \
1935
                        isinstance(link['meter']['uuid'], str) and \
1936
                        len(str.strip(link['meter']['uuid'])) > 0:
1937
                    meter_uuid = str.strip(link['meter']['uuid'])
1938
1939
            cursor.execute(" SELECT name "
1940
                           " FROM tbl_energy_flow_diagrams "
1941
                           " WHERE id = %s ", (new_id,))
1942
            if cursor.fetchone() is None:
1943
                cursor.close()
1944
                cnx.close()
1945
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.NOT_FOUND',
1946
                                       description='API.ENERGY_FLOW_DIAGRAM_NOT_FOUND')
1947
1948
            cursor.execute(" SELECT id "
1949
                           " FROM tbl_energy_flow_diagrams_links "
1950
                           " WHERE energy_flow_diagram_id = %s AND "
1951
                           "       source_node_id = %s AND target_node_id = %s ",
1952
                           (new_id, source_node_id, target_node_id,))
1953
            row = cursor.fetchone()
1954
            if row is not None:
1955
                cursor.close()
1956
                cnx.close()
1957
                raise falcon.HTTPError(status=falcon.HTTP_400,
1958
                                       title='API.NOT_FOUND',
1959
                                       description='API.ENERGY_FLOW_DIAGRAM_LINK_IS_ALREADY_IN_USE')
1960
1961
            query = (" SELECT id, name "
1962
                     " FROM tbl_energy_flow_diagrams_nodes "
1963
                     " WHERE id = %s ")
1964
            cursor.execute(query, (source_node_id,))
1965
            if cursor.fetchone() is None:
1966
                cursor.close()
1967
                cnx.close()
1968
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1969
                                       description='API.SOURCE_NODE_NOT_FOUND')
1970
1971
            query = (" SELECT id, name "
1972
                     " FROM tbl_energy_flow_diagrams_nodes "
1973
                     " WHERE id = %s ")
1974
            cursor.execute(query, (target_node_id,))
1975
            if cursor.fetchone() is None:
1976
                cursor.close()
1977
                cnx.close()
1978
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1979
                                       description='API.TARGET_NODE_NOT_FOUND')
1980
1981
            query = (" SELECT id, name, uuid "
1982
                     " FROM tbl_meters ")
1983
            cursor.execute(query)
1984
            rows_meters = cursor.fetchall()
1985
1986
            meter_dict = dict()
1987
            if rows_meters is not None and len(rows_meters) > 0:
1988
                for row in rows_meters:
1989
                    meter_dict[row[2]] = {"type": 'meter',
1990
                                          "id": row[0],
1991
                                          "name": row[1],
1992
                                          "uuid": row[2]}
1993
1994
            query = (" SELECT id, name, uuid "
1995
                     " FROM tbl_offline_meters ")
1996
            cursor.execute(query)
1997
            rows_offline_meters = cursor.fetchall()
1998
1999
            offline_meter_dict = dict()
2000
            if rows_offline_meters is not None and len(rows_offline_meters) > 0:
2001
                for row in rows_offline_meters:
2002
                    offline_meter_dict[row[2]] = {"type": 'offline_meter',
2003
                                                  "id": row[0],
2004
                                                  "name": row[1],
2005
                                                  "uuid": row[2]}
2006
2007
            query = (" SELECT id, name, uuid "
2008
                     " FROM tbl_virtual_meters ")
2009
            cursor.execute(query)
2010
            rows_virtual_meters = cursor.fetchall()
2011
2012
            virtual_meter_dict = dict()
2013
            if rows_virtual_meters is not None and len(rows_virtual_meters) > 0:
2014
                for row in rows_virtual_meters:
2015
                    virtual_meter_dict[row[2]] = {"type": 'virtual_meter',
2016
                                                  "id": row[0],
2017
                                                  "name": row[1],
2018
                                                  "uuid": row[2]}
2019
2020
            # validate meter uuid
2021
            if meter_dict.get(meter_uuid) is None and \
2022
                    virtual_meter_dict.get(meter_uuid) is None and \
2023
                    offline_meter_dict.get(meter_uuid) is None:
2024
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
2025
                                       description='API.INVALID_METER_UUID')
2026
2027
            add_values = (" INSERT INTO tbl_energy_flow_diagrams_links "
2028
                          "    (energy_flow_diagram_id, source_node_id, target_node_id, meter_uuid) "
2029
                          " VALUES (%s, %s, %s, %s) ")
2030
            cursor.execute(add_values, (new_id,
2031
                                        source_node_id,
2032
                                        target_node_id,
2033
                                        meter_uuid))
2034
        cnx.commit()
2035
        cursor.close()
2036
        cnx.close()
2037
2038
        # Clear cache after importing diagram
2039
        clear_energy_flow_diagram_cache()
2040
2041
        resp.status = falcon.HTTP_201
2042
        resp.location = '/energyflowdiagrams/' + str(new_id)
2043
2044
2045
class EnergyFlowDiagramClone:
2046
    def __init__(self):
2047
        pass
2048
2049
    @staticmethod
2050
    def on_options(req, resp, id_):
2051
        _ = req
2052
        resp.status = falcon.HTTP_200
2053
        _ = id_
2054
2055
    @staticmethod
2056
    @user_logger
2057
    def on_post(req, resp, id_):
2058
        admin_control(req)
2059
        if not id_.isdigit() or int(id_) <= 0:
2060
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
2061
                                   description='API.INVALID_ENERGY_FLOW_DIAGRAM_ID')
2062
2063
        cnx = mysql.connector.connect(**config.myems_system_db)
2064
        cursor = cnx.cursor()
2065
2066
        query = (" SELECT id, name, uuid "
2067
                 " FROM tbl_meters ")
2068
        cursor.execute(query)
2069
        rows_meters = cursor.fetchall()
2070
2071
        meter_dict = dict()
2072
        if rows_meters is not None and len(rows_meters) > 0:
2073
            for row in rows_meters:
2074
                meter_dict[row[2]] = {"type": 'meter',
2075
                                      "id": row[0],
2076
                                      "name": row[1],
2077
                                      "uuid": row[2]}
2078
2079
        query = (" SELECT id, name, uuid "
2080
                 " FROM tbl_offline_meters ")
2081
        cursor.execute(query)
2082
        rows_offline_meters = cursor.fetchall()
2083
2084
        offline_meter_dict = dict()
2085
        if rows_offline_meters is not None and len(rows_offline_meters) > 0:
2086
            for row in rows_offline_meters:
2087
                offline_meter_dict[row[2]] = {"type": 'offline_meter',
2088
                                              "id": row[0],
2089
                                              "name": row[1],
2090
                                              "uuid": row[2]}
2091
2092
        query = (" SELECT id, name, uuid "
2093
                 " FROM tbl_virtual_meters ")
2094
        cursor.execute(query)
2095
        rows_virtual_meters = cursor.fetchall()
2096
2097
        virtual_meter_dict = dict()
2098
        if rows_virtual_meters is not None and len(rows_virtual_meters) > 0:
2099
            for row in rows_virtual_meters:
2100
                virtual_meter_dict[row[2]] = {"type": 'virtual_meter',
2101
                                              "id": row[0],
2102
                                              "name": row[1],
2103
                                              "uuid": row[2]}
2104
2105
        query = (" SELECT id, energy_flow_diagram_id, name "
2106
                 " FROM tbl_energy_flow_diagrams_nodes")
2107
        cursor.execute(query)
2108
        rows_nodes = cursor.fetchall()
2109
2110
        node_dict = dict()
2111
        node_list_dict = dict()
2112
        if rows_nodes is not None and len(rows_nodes) > 0:
2113
            for row in rows_nodes:
2114
                node_dict[row[0]] = row[2]
2115
                if node_list_dict.get(row[1]) is None:
2116
                    node_list_dict[row[1]] = list()
2117
                node_list_dict[row[1]].append({"id": row[0], "name": row[2]})
2118
2119
        query = (" SELECT id, energy_flow_diagram_id, source_node_id, target_node_id, meter_uuid "
2120
                 " FROM tbl_energy_flow_diagrams_links")
2121
        cursor.execute(query)
2122
        rows_links = cursor.fetchall()
2123
2124
        link_list_dict = dict()
2125
        if rows_links is not None and len(rows_links) > 0:
2126
            for row in rows_links:
2127
                # find meter by uuid
2128
                meter = meter_dict.get(row[4], None)
2129
                if meter is None:
2130
                    meter = virtual_meter_dict.get(row[4], None)
2131
                if meter is None:
2132
                    meter = offline_meter_dict.get(row[4], None)
2133
2134
                if link_list_dict.get(row[1]) is None:
2135
                    link_list_dict[row[1]] = list()
2136
                link_list_dict[row[1]].append({"id": row[0],
2137
                                               "source_node": {
2138
                                                   "id": row[2],
2139
                                                   "name": node_dict.get(row[2])},
2140
                                               "target_node": {
2141
                                                   "id": row[3],
2142
                                                   "name": node_dict.get(row[3])},
2143
                                               "meter": meter})
2144
2145
        query = (" SELECT id, name, uuid "
2146
                 " FROM tbl_energy_flow_diagrams "
2147
                 " WHERE id = %s ")
2148
        cursor.execute(query, (id_,))
2149
        row = cursor.fetchone()
2150
2151
        if row is None:
2152
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
2153
                                   description='API.ENERGY_FLOW_DIAGRAM_NOT_FOUND')
2154
        else:
2155
            meta_result = {"id": row[0],
2156
                           "name": row[1],
2157
                           "uuid": row[2],
2158
                           "nodes": node_list_dict.get(row[0], None),
2159
                           "links": link_list_dict.get(row[0], None),
2160
                           }
2161
            timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
2162
            if config.utc_offset[0] == '-':
2163
                timezone_offset = -timezone_offset
2164
            new_name = (str.strip(meta_result['name']) +
2165
                        (datetime.utcnow() + timedelta(minutes=timezone_offset)).isoformat(sep='-', timespec='seconds'))
2166
            add_values = (" INSERT INTO tbl_energy_flow_diagrams "
2167
                          "    (name, uuid) "
2168
                          " VALUES (%s, %s) ")
2169
            cursor.execute(add_values, (new_name,
2170
                                        str(uuid.uuid4())))
2171
            new_id = cursor.lastrowid
2172
            if meta_result['nodes'] is not None and len(meta_result['nodes']) > 0:
2173
                for node in meta_result['nodes']:
2174
                    add_values = (" INSERT INTO tbl_energy_flow_diagrams_nodes "
2175
                                  "    (energy_flow_diagram_id, name) "
2176
                                  " VALUES (%s, %s) ")
2177
                    cursor.execute(add_values, (new_id,
2178
                                                node['name']))
2179
            if meta_result['links'] is not None and len(meta_result['links']) > 0:
2180
                for link in meta_result['links']:
2181
                    if link['meter'] is None:
2182
                        continue
2183
                    add_values = (" INSERT INTO tbl_energy_flow_diagrams_links "
2184
                                  "    (energy_flow_diagram_id, source_node_id, target_node_id, meter_uuid) "
2185
                                  " VALUES (%s, %s, %s, %s) ")
2186
                    cursor.execute(add_values, (new_id,
2187
                                                link['source_node']['id'],
2188
                                                link['target_node']['id'],
2189
                                                link['meter']['uuid']))
2190
            cnx.commit()
2191
            cursor.close()
2192
            cnx.close()
2193
2194
            # Clear cache after cloning diagram
2195
            clear_energy_flow_diagram_cache()
2196
2197
            resp.status = falcon.HTTP_201
2198
            resp.location = '/energyflowdiagrams/' + str(new_id)
2199
2200