core.sensor.SensorClone.on_post()   D
last analyzed

Complexity

Conditions 12

Size

Total Lines 77
Code Lines 58

Duplication

Lines 24
Ratio 31.17 %

Importance

Changes 0
Metric Value
eloc 58
dl 24
loc 77
rs 4.7345
c 0
b 0
f 0
cc 12
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like core.sensor.SensorClone.on_post() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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 View Code Duplication
def clear_sensor_cache(sensor_id=None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
12
    """
13
    Clear sensor-related cache after data modification
14
15
    Args:
16
        sensor_id: Sensor ID (optional, for specific sensor 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 sensor list cache (all search query variations)
36
        list_cache_key_pattern = 'sensor: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 sensor item cache if sensor_id is provided
42
        if sensor_id:
43
            item_cache_key = f'sensor:item:{sensor_id}'
44
            redis_client.delete(item_cache_key)
45
            point_cache_key = f'sensor:point:{sensor_id}'
46
            redis_client.delete(point_cache_key)
47
            export_cache_key = f'sensor:export:{sensor_id}'
48
            redis_client.delete(export_cache_key)
49
50
    except Exception:
51
        # If cache clear fails, ignore and continue
52
        pass
53
54
55
class SensorCollection:
56
    """
57
    Sensor Collection Resource
58
59
    This class handles CRUD operations for sensor collection.
60
    It provides endpoints for listing all sensors and creating new sensors.
61
    Sensors represent data collection devices in the energy management system.
62
    """
63
64
    def __init__(self):
65
        """Initialize SensorCollection"""
66
        pass
67
68
    @staticmethod
69
    def on_options(req, resp):
70
        """Handle OPTIONS requests for CORS preflight"""
71
        _ = req
72
        resp.status = falcon.HTTP_200
73
74
    @staticmethod
75
    def on_get(req, resp):
76
        if 'API-KEY' not in req.headers or \
77
                not isinstance(req.headers['API-KEY'], str) or \
78
                len(str.strip(req.headers['API-KEY'])) == 0:
79
            access_control(req)
80
        else:
81
            api_key_control(req)
82
83
        search_query = req.get_param('q', default=None)
84
        if search_query is not None:
85
            search_query = search_query.strip()
86
        else:
87
            search_query = ''
88
89
        # Redis cache key
90
        cache_key = f'sensor:list:{search_query}'
91
        cache_expire = 28800  # 8 hours in seconds (long-term cache)
92
93
        # Try to get from Redis cache (only if Redis is enabled)
94
        redis_client = None
95
        if config.redis.get('is_enabled', False):
96
            try:
97
                redis_client = redis.Redis(
98
                    host=config.redis['host'],
99
                    port=config.redis['port'],
100
                    password=config.redis['password'] if config.redis['password'] else None,
101
                    db=config.redis['db'],
102
                    decode_responses=True,
103
                    socket_connect_timeout=2,
104
                    socket_timeout=2
105
                )
106
                redis_client.ping()
107
                cached_result = redis_client.get(cache_key)
108
                if cached_result:
109
                    resp.text = cached_result
110
                    return
111
            except Exception:
112
                # If Redis connection fails, continue to database query
113
                pass
114
115
        # Cache miss or Redis error - query database
116
        cnx = mysql.connector.connect(**config.myems_system_db)
117
        cursor = cnx.cursor()
118
119
        query = (" SELECT id, name, uuid, description "
120
                 " FROM tbl_sensors ")
121
122
        params = []
123
        if search_query:
124
            query += " WHERE name LIKE %s   OR  description LIKE %s "
125
            params = [f'%{search_query}%', f'%{search_query}%']
126
        query += " ORDER BY id "
127
        cursor.execute(query, params)
128
129
        rows_sensors = cursor.fetchall()
130
131
        result = list()
132
        if rows_sensors is not None and len(rows_sensors) > 0:
133
            for row in rows_sensors:
134
                meta_result = {"id": row[0],
135
                               "name": row[1],
136
                               "uuid": row[2],
137
                               "description": row[3]}
138
                result.append(meta_result)
139
140
        cursor.close()
141
        cnx.close()
142
143
        # Store result in Redis cache
144
        result_json = json.dumps(result)
145
        if redis_client:
146
            try:
147
                redis_client.setex(cache_key, cache_expire, result_json)
148
            except Exception:
149
                # If cache set fails, ignore and continue
150
                pass
151
152
        resp.text = result_json
153
154 View Code Duplication
    @staticmethod
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
155
    @user_logger
156
    def on_post(req, resp):
157
        """Handles POST requests"""
158
        admin_control(req)
159
        try:
160
            raw_json = req.stream.read().decode('utf-8')
161
        except UnicodeDecodeError as ex:
162
            print("Failed to decode request")
163
            raise falcon.HTTPError(status=falcon.HTTP_400,
164
                                   title='API.BAD_REQUEST',
165
                                   description='API.INVALID_ENCODING')
166
        except Exception as ex:
167
            print("Unexpected error reading request stream")
168
            raise falcon.HTTPError(status=falcon.HTTP_400,
169
                                   title='API.BAD_REQUEST',
170
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
171
172
        new_values = json.loads(raw_json)
173
174
        if 'name' not in new_values['data'].keys() or \
175
                not isinstance(new_values['data']['name'], str) or \
176
                len(str.strip(new_values['data']['name'])) == 0:
177
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
178
                                   description='API.INVALID_SENSOR_NAME')
179
        name = str.strip(new_values['data']['name'])
180
181
        if 'description' in new_values['data'].keys() and \
182
                new_values['data']['description'] is not None and \
183
                len(str(new_values['data']['description'])) > 0:
184
            description = str.strip(new_values['data']['description'])
185
        else:
186
            description = None
187
188
        cnx = mysql.connector.connect(**config.myems_system_db)
189
        cursor = cnx.cursor()
190
191
        cursor.execute(" SELECT name "
192
                       " FROM tbl_sensors "
193
                       " WHERE name = %s ", (name,))
194
        if cursor.fetchone() is not None:
195
            cursor.close()
196
            cnx.close()
197
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
198
                                   description='API.SENSOR_NAME_IS_ALREADY_IN_USE')
199
200
        add_values = (" INSERT INTO tbl_sensors "
201
                      "    (name, uuid, description) "
202
                      " VALUES (%s, %s, %s) ")
203
        cursor.execute(add_values, (name,
204
                                    str(uuid.uuid4()),
205
                                    description))
206
        new_id = cursor.lastrowid
207
        cnx.commit()
208
        cursor.close()
209
        cnx.close()
210
211
        # Clear cache after creating new sensor
212
        clear_sensor_cache()
213
214
        resp.status = falcon.HTTP_201
215
        resp.location = '/sensors/' + str(new_id)
216
217
218
class SensorItem:
219
    def __init__(self):
220
        pass
221
222
    @staticmethod
223
    def on_options(req, resp, id_):
224
        _ = req
225
        resp.status = falcon.HTTP_200
226
        _ = id_
227
228
    @staticmethod
229
    def on_get(req, resp, id_):
230
        if 'API-KEY' not in req.headers or \
231
                not isinstance(req.headers['API-KEY'], str) or \
232
                len(str.strip(req.headers['API-KEY'])) == 0:
233
            access_control(req)
234
        else:
235
            api_key_control(req)
236
        if not id_.isdigit() or int(id_) <= 0:
237
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
238
                                   description='API.INVALID_SENSOR_ID')
239
240
        # Redis cache key
241
        cache_key = f'sensor:item:{id_}'
242
        cache_expire = 28800  # 8 hours in seconds (long-term cache)
243
244
        # Try to get from Redis cache (only if Redis is enabled)
245
        redis_client = None
246
        if config.redis.get('is_enabled', False):
247
            try:
248
                redis_client = redis.Redis(
249
                    host=config.redis['host'],
250
                    port=config.redis['port'],
251
                    password=config.redis['password'] if config.redis['password'] else None,
252
                    db=config.redis['db'],
253
                    decode_responses=True,
254
                    socket_connect_timeout=2,
255
                    socket_timeout=2
256
                )
257
                redis_client.ping()
258
                cached_result = redis_client.get(cache_key)
259
                if cached_result:
260
                    resp.text = cached_result
261
                    return
262
            except Exception:
263
                # If Redis connection fails, continue to database query
264
                pass
265
266
        # Cache miss or Redis error - query database
267
        cnx = mysql.connector.connect(**config.myems_system_db)
268
        cursor = cnx.cursor()
269
270
        query = (" SELECT id, name, uuid, description "
271
                 " FROM tbl_sensors "
272
                 " WHERE id = %s ")
273
        cursor.execute(query, (id_,))
274
        row = cursor.fetchone()
275
        cursor.close()
276
        cnx.close()
277
278
        if row is None:
279
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
280
                                   description='API.SENSOR_NOT_FOUND')
281
        else:
282
            meta_result = {"id": row[0],
283
                           "name": row[1],
284
                           "uuid": row[2],
285
                           "description": row[3]}
286
287
        # Store result in Redis cache
288
        result_json = json.dumps(meta_result)
289
        if redis_client:
290
            try:
291
                redis_client.setex(cache_key, cache_expire, result_json)
292
            except Exception:
293
                # If cache set fails, ignore and continue
294
                pass
295
296
        resp.text = result_json
297
298
    @staticmethod
299
    @user_logger
300
    def on_delete(req, resp, id_):
301
        admin_control(req)
302
        if not id_.isdigit() or int(id_) <= 0:
303
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
304
                                   description='API.INVALID_SENSOR_ID')
305
306
        cnx = mysql.connector.connect(**config.myems_system_db)
307
        cursor = cnx.cursor()
308
309
        cursor.execute(" SELECT name "
310
                       " FROM tbl_sensors "
311
                       " WHERE id = %s ", (id_,))
312
        if cursor.fetchone() is None:
313
            cursor.close()
314
            cnx.close()
315
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
316
                                   description='API.SENSOR_NOT_FOUND')
317
318
        # check relation with spaces
319
        cursor.execute(" SELECT id "
320
                       " FROM tbl_spaces_sensors "
321
                       " WHERE sensor_id = %s ", (id_,))
322
        rows_spaces = cursor.fetchall()
323
        if rows_spaces is not None and len(rows_spaces) > 0:
324
            cursor.close()
325
            cnx.close()
326
            raise falcon.HTTPError(status=falcon.HTTP_400,
327
                                   title='API.BAD_REQUEST',
328
                                   description='API.THERE_IS_RELATION_WITH_SPACES')
329
330
        # check relation with tenants
331
        cursor.execute(" SELECT id "
332
                       " FROM tbl_tenants_sensors "
333
                       " WHERE sensor_id = %s ", (id_,))
334
        rows_tenants = cursor.fetchall()
335
        if rows_tenants is not None and len(rows_tenants) > 0:
336
            cursor.close()
337
            cnx.close()
338
            raise falcon.HTTPError(status=falcon.HTTP_400,
339
                                   title='API.BAD_REQUEST',
340
                                   description='API.THERE_IS_RELATION_WITH_TENANTS')
341
342
        # check relation with stores
343
        cursor.execute(" SELECT store_id "
344
                       " FROM tbl_stores_sensors "
345
                       " WHERE sensor_id = %s ", (id_,))
346
        rows_stores = cursor.fetchall()
347
        if rows_stores is not None and len(rows_stores) > 0:
348
            cursor.close()
349
            cnx.close()
350
            raise falcon.HTTPError(status=falcon.HTTP_400,
351
                                   title='API.BAD_REQUEST',
352
                                   description='API.THERE_IS_RELATION_WITH_STORES')
353
354
        # check relation with microgrid
355
        cursor.execute(" SELECT id "
356
                       " FROM tbl_microgrids_sensors "
357
                       " WHERE sensor_id = %s ", (id_,))
358
        rows_microgrid = cursor.fetchall()
359
        if rows_microgrid is not None and len(rows_microgrid) > 0:
360
            cursor.close()
361
            cnx.close()
362
            raise falcon.HTTPError(status=falcon.HTTP_400,
363
                                   title='API.BAD_REQUEST',
364
                                   description='API.THERE_IS_RELATION_WITH_MICROGRIDS')
365
366
        # check relation with shopfloors
367
        cursor.execute(" SELECT id "
368
                       " FROM tbl_shopfloors_sensors "
369
                       " WHERE sensor_id = %s ", (id_,))
370
        rows_shopfloor = cursor.fetchall()
371
        if rows_shopfloor is not None and len(rows_shopfloor) > 0:
372
            cursor.close()
373
            cnx.close()
374
            raise falcon.HTTPError(status=falcon.HTTP_400,
375
                                   title='API.BAD_REQUEST',
376
                                   description='API.THERE_IS_RELATION_WITH_SHOPFLOORS')
377
378
        # delete relation with points
379
        cursor.execute(" DELETE FROM tbl_sensors_points WHERE sensor_id = %s ", (id_,))
380
381
        cursor.execute(" DELETE FROM tbl_sensors WHERE id = %s ", (id_,))
382
        cnx.commit()
383
384
        cursor.close()
385
        cnx.close()
386
387
        # Clear cache after deleting sensor
388
        clear_sensor_cache(sensor_id=id_)
389
390
        resp.status = falcon.HTTP_204
391
392 View Code Duplication
    @staticmethod
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
393
    @user_logger
394
    def on_put(req, resp, id_):
395
        """Handles PUT requests"""
396
        admin_control(req)
397
        try:
398
            raw_json = req.stream.read().decode('utf-8')
399
        except UnicodeDecodeError as ex:
400
            print("Failed to decode request")
401
            raise falcon.HTTPError(status=falcon.HTTP_400,
402
                                   title='API.BAD_REQUEST',
403
                                   description='API.INVALID_ENCODING')
404
        except Exception as ex:
405
            print("Unexpected error reading request stream")
406
            raise falcon.HTTPError(status=falcon.HTTP_400,
407
                                   title='API.BAD_REQUEST',
408
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
409
410
        if not id_.isdigit() or int(id_) <= 0:
411
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
412
                                   description='API.INVALID_SENSOR_ID')
413
414
        new_values = json.loads(raw_json)
415
416
        if 'name' not in new_values['data'].keys() or \
417
                not isinstance(new_values['data']['name'], str) or \
418
                len(str.strip(new_values['data']['name'])) == 0:
419
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
420
                                   description='API.INVALID_SENSOR_NAME')
421
        name = str.strip(new_values['data']['name'])
422
423
        if 'description' in new_values['data'].keys() and \
424
                new_values['data']['description'] is not None and \
425
                len(str(new_values['data']['description'])) > 0:
426
            description = str.strip(new_values['data']['description'])
427
        else:
428
            description = None
429
430
        cnx = mysql.connector.connect(**config.myems_system_db)
431
        cursor = cnx.cursor()
432
433
        cursor.execute(" SELECT name "
434
                       " FROM tbl_sensors "
435
                       " WHERE id = %s ", (id_,))
436
        if cursor.fetchone() is None:
437
            cursor.close()
438
            cnx.close()
439
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
440
                                   description='API.SENSOR_NOT_FOUND')
441
442
        cursor.execute(" SELECT name "
443
                       " FROM tbl_sensors "
444
                       " WHERE name = %s AND id != %s ", (name, id_))
445
        if cursor.fetchone() is not None:
446
            cursor.close()
447
            cnx.close()
448
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
449
                                   description='API.SENSOR_NAME_IS_ALREADY_IN_USE')
450
451
        update_row = (" UPDATE tbl_sensors "
452
                      " SET name = %s, description = %s "
453
                      " WHERE id = %s ")
454
        cursor.execute(update_row, (name,
455
                                    description,
456
                                    id_,))
457
        cnx.commit()
458
459
        cursor.close()
460
        cnx.close()
461
462
        # Clear cache after updating sensor
463
        clear_sensor_cache(sensor_id=id_)
464
465
        resp.status = falcon.HTTP_200
466
467
468
class SensorPointCollection:
469
    def __init__(self):
470
        pass
471
472
    @staticmethod
473
    def on_options(req, resp, id_):
474
        _ = req
475
        resp.status = falcon.HTTP_200
476
        _ = id_
477
478 View Code Duplication
    @staticmethod
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
479
    def on_get(req, resp, id_):
480
        if 'API-KEY' not in req.headers or \
481
                not isinstance(req.headers['API-KEY'], str) or \
482
                len(str.strip(req.headers['API-KEY'])) == 0:
483
            access_control(req)
484
        else:
485
            api_key_control(req)
486
        if not id_.isdigit() or int(id_) <= 0:
487
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
488
                                   description='API.INVALID_SENSOR_ID')
489
490
        # Redis cache key
491
        cache_key = f'sensor:point:{id_}'
492
        cache_expire = 28800  # 8 hours in seconds (long-term cache)
493
494
        # Try to get from Redis cache (only if Redis is enabled)
495
        redis_client = None
496
        if config.redis.get('is_enabled', False):
497
            try:
498
                redis_client = redis.Redis(
499
                    host=config.redis['host'],
500
                    port=config.redis['port'],
501
                    password=config.redis['password'] if config.redis['password'] else None,
502
                    db=config.redis['db'],
503
                    decode_responses=True,
504
                    socket_connect_timeout=2,
505
                    socket_timeout=2
506
                )
507
                redis_client.ping()
508
                cached_result = redis_client.get(cache_key)
509
                if cached_result:
510
                    resp.text = cached_result
511
                    return
512
            except Exception:
513
                # If Redis connection fails, continue to database query
514
                pass
515
516
        # Cache miss or Redis error - query database
517
        cnx = mysql.connector.connect(**config.myems_system_db)
518
        cursor = cnx.cursor()
519
520
        cursor.execute(" SELECT name "
521
                       " FROM tbl_sensors "
522
                       " WHERE id = %s ", (id_,))
523
        if cursor.fetchone() is None:
524
            cursor.close()
525
            cnx.close()
526
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
527
                                   description='API.SENSOR_NOT_FOUND')
528
529
        query = (" SELECT p.id, p.name, "
530
                 "        ds.id, ds.name, ds.uuid, "
531
                 "        p.address "
532
                 " FROM tbl_points p, tbl_sensors_points sp, tbl_data_sources ds "
533
                 " WHERE sp.sensor_id = %s AND p.id = sp.point_id AND p.data_source_id = ds.id "
534
                 " ORDER BY p.name ")
535
        cursor.execute(query, (id_,))
536
        rows = cursor.fetchall()
537
538
        result = list()
539
        if rows is not None and len(rows) > 0:
540
            for row in rows:
541
                meta_result = {"id": row[0], "name": row[1],
542
                               "data_source": {"id": row[2], "name": row[3], "uuid": row[4]},
543
                               "address": row[5]}
544
                result.append(meta_result)
545
546
        cursor.close()
547
        cnx.close()
548
549
        # Store result in Redis cache
550
        result_json = json.dumps(result)
551
        if redis_client:
552
            try:
553
                redis_client.setex(cache_key, cache_expire, result_json)
554
            except Exception:
555
                # If cache set fails, ignore and continue
556
                pass
557
558
        resp.text = result_json
559
560 View Code Duplication
    @staticmethod
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
561
    @user_logger
562
    def on_post(req, resp, id_):
563
        """Handles POST requests"""
564
        admin_control(req)
565
        try:
566
            raw_json = req.stream.read().decode('utf-8')
567
        except UnicodeDecodeError as ex:
568
            print("Failed to decode request")
569
            raise falcon.HTTPError(status=falcon.HTTP_400,
570
                                   title='API.BAD_REQUEST',
571
                                   description='API.INVALID_ENCODING')
572
        except Exception as ex:
573
            print("Unexpected error reading request stream")
574
            raise falcon.HTTPError(status=falcon.HTTP_400,
575
                                   title='API.BAD_REQUEST',
576
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
577
578
        if not id_.isdigit() or int(id_) <= 0:
579
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
580
                                   description='API.INVALID_SENSOR_ID')
581
582
        new_values = json.loads(raw_json)
583
584
        cnx = mysql.connector.connect(**config.myems_system_db)
585
        cursor = cnx.cursor()
586
587
        cursor.execute(" SELECT name "
588
                       " from tbl_sensors "
589
                       " WHERE id = %s ", (id_,))
590
        if cursor.fetchone() is None:
591
            cursor.close()
592
            cnx.close()
593
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
594
                                   description='API.SENSOR_NOT_FOUND')
595
596
        cursor.execute(" SELECT name "
597
                       " FROM tbl_points "
598
                       " WHERE id = %s ", (new_values['data']['point_id'],))
599
        if cursor.fetchone() is None:
600
            cursor.close()
601
            cnx.close()
602
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
603
                                   description='API.POINT_NOT_FOUND')
604
605
        query = (" SELECT id "
606
                 " FROM tbl_sensors_points "
607
                 " WHERE sensor_id = %s AND point_id = %s")
608
        cursor.execute(query, (id_, new_values['data']['point_id'],))
609
        if cursor.fetchone() is not None:
610
            cursor.close()
611
            cnx.close()
612
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.ERROR',
613
                                   description='API.SENSOR_POINT_RELATION_EXISTS')
614
615
        add_row = (" INSERT INTO tbl_sensors_points (sensor_id, point_id) "
616
                   " VALUES (%s, %s) ")
617
        cursor.execute(add_row, (id_, new_values['data']['point_id'],))
618
        cnx.commit()
619
        cursor.close()
620
        cnx.close()
621
622
        # Clear cache after adding sensor point
623
        clear_sensor_cache(sensor_id=id_)
624
625
        resp.status = falcon.HTTP_201
626
        resp.location = '/sensors/' + str(id_) + '/points/' + str(new_values['data']['point_id'])
627
628
629
class SensorPointItem:
630
    def __init__(self):
631
        pass
632
633
    @staticmethod
634
    def on_options(req, resp, id_, pid):
635
        _ = req
636
        resp.status = falcon.HTTP_200
637
        _ = id_
638
639
    @staticmethod
640
    @user_logger
641
    def on_delete(req, resp, id_, pid):
642
        admin_control(req)
643
        if not id_.isdigit() or int(id_) <= 0:
644
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
645
                                   description='API.INVALID_SENSOR_ID')
646
647
        if not pid.isdigit() or int(pid) <= 0:
648
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
649
                                   description='API.INVALID_POINT_ID')
650
651
        cnx = mysql.connector.connect(**config.myems_system_db)
652
        cursor = cnx.cursor()
653
654
        cursor.execute(" SELECT name "
655
                       " FROM tbl_sensors "
656
                       " WHERE id = %s ", (id_,))
657
        if cursor.fetchone() is None:
658
            cursor.close()
659
            cnx.close()
660
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
661
                                   description='API.SENSOR_NOT_FOUND')
662
663
        cursor.execute(" SELECT name "
664
                       " FROM tbl_points "
665
                       " WHERE id = %s ", (pid,))
666
        if cursor.fetchone() is None:
667
            cursor.close()
668
            cnx.close()
669
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
670
                                   description='API.POINT_NOT_FOUND')
671
672
        cursor.execute(" SELECT id "
673
                       " FROM tbl_sensors_points "
674
                       " WHERE sensor_id = %s AND point_id = %s ", (id_, pid))
675
        if cursor.fetchone() is None:
676
            cursor.close()
677
            cnx.close()
678
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
679
                                   description='API.SENSOR_POINT_RELATION_NOT_FOUND')
680
681
        cursor.execute(" DELETE FROM tbl_sensors_points WHERE sensor_id = %s AND point_id = %s ", (id_, pid))
682
        cnx.commit()
683
684
        cursor.close()
685
        cnx.close()
686
687
        # Clear cache after deleting sensor point
688
        clear_sensor_cache(sensor_id=id_)
689
690
        resp.status = falcon.HTTP_204
691
692
693
class SensorExport:
694
    def __init__(self):
695
        pass
696
697
    @staticmethod
698
    def on_options(req, resp, id_):
699
        _ = req
700
        resp.status = falcon.HTTP_200
701
        _ = id_
702
703
    @staticmethod
704
    def on_get(req, resp, id_):
705
        if 'API-KEY' not in req.headers or \
706
                not isinstance(req.headers['API-KEY'], str) or \
707
                len(str.strip(req.headers['API-KEY'])) == 0:
708
            access_control(req)
709
        else:
710
            api_key_control(req)
711
        if not id_.isdigit() or int(id_) <= 0:
712
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
713
                                   description='API.INVALID_SENSOR_ID')
714
715
        # Redis cache key
716
        cache_key = f'sensor:export:{id_}'
717
        cache_expire = 28800  # 8 hours in seconds (long-term cache)
718
719
        # Try to get from Redis cache (only if Redis is enabled)
720
        redis_client = None
721
        if config.redis.get('is_enabled', False):
722
            try:
723
                redis_client = redis.Redis(
724
                    host=config.redis['host'],
725
                    port=config.redis['port'],
726
                    password=config.redis['password'] if config.redis['password'] else None,
727
                    db=config.redis['db'],
728
                    decode_responses=True,
729
                    socket_connect_timeout=2,
730
                    socket_timeout=2
731
                )
732
                redis_client.ping()
733
                cached_result = redis_client.get(cache_key)
734
                if cached_result:
735
                    resp.text = cached_result
736
                    return
737
            except Exception:
738
                # If Redis connection fails, continue to database query
739
                pass
740
741
        # Cache miss or Redis error - query database
742
        cnx = mysql.connector.connect(**config.myems_system_db)
743
        cursor = cnx.cursor()
744
745
        query = (" SELECT id, name, uuid, description "
746
                 " FROM tbl_sensors "
747
                 " WHERE id = %s ")
748
        cursor.execute(query, (id_,))
749
        row = cursor.fetchone()
750
751 View Code Duplication
        if row is None:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
752
            cursor.close()
753
            cnx.close()
754
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
755
                                   description='API.SENSOR_NOT_FOUND')
756
        else:
757
            meta_result = {"id": row[0],
758
                           "name": row[1],
759
                           "uuid": row[2],
760
                           "description": row[3],
761
                           "points": None}
762
            query = (" SELECT p.id, p.name "
763
                     " FROM tbl_points p, tbl_sensors_points sp "
764
                     " WHERE sp.sensor_id = %s AND p.id = sp.point_id "
765
                     " ORDER BY p.id ")
766
            cursor.execute(query, (id_,))
767
            rows = cursor.fetchall()
768
            result = list()
769
            if rows is not None and len(rows) > 0:
770
                for row in rows:
771
                    point_result = {"id": row[0], "name": row[1]}
772
                    result.append(point_result)
773
                meta_result['points'] = result
774
            cursor.close()
775
            cnx.close()
776
777
        # Store result in Redis cache
778
        result_json = json.dumps(meta_result)
779
        if redis_client:
780
            try:
781
                redis_client.setex(cache_key, cache_expire, result_json)
782
            except Exception:
783
                # If cache set fails, ignore and continue
784
                pass
785
786
        resp.text = result_json
787
788
789
class SensorImport:
790
    def __init__(self):
791
        pass
792
793
    @staticmethod
794
    def on_options(req, resp):
795
        _ = req
796
        resp.status = falcon.HTTP_200
797
798
    @staticmethod
799
    @user_logger
800
    def on_post(req, resp):
801
        """Handles POST requests"""
802
        admin_control(req)
803
        try:
804
            raw_json = req.stream.read().decode('utf-8')
805
        except UnicodeDecodeError as ex:
806
            print("Failed to decode request")
807
            raise falcon.HTTPError(status=falcon.HTTP_400,
808
                                   title='API.BAD_REQUEST',
809
                                   description='API.INVALID_ENCODING')
810
        except Exception as ex:
811
            print("Unexpected error reading request stream")
812
            raise falcon.HTTPError(status=falcon.HTTP_400,
813
                                   title='API.BAD_REQUEST',
814
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
815
816
        new_values = json.loads(raw_json)
817
818
        if 'name' not in new_values.keys() or \
819
                not isinstance(new_values['name'], str) or \
820
                len(str.strip(new_values['name'])) == 0:
821
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
822
                                   description='API.INVALID_SENSOR_NAME')
823
        name = str.strip(new_values['name'])
824
825
        if 'description' in new_values.keys() and \
826
                new_values['description'] is not None and \
827
                len(str(new_values['description'])) > 0:
828
            description = str.strip(new_values['description'])
829
        else:
830
            description = None
831
832
        cnx = mysql.connector.connect(**config.myems_system_db)
833
        cursor = cnx.cursor()
834
835
        cursor.execute(" SELECT name "
836
                       " FROM tbl_sensors "
837
                       " WHERE name = %s ", (name,))
838
        if cursor.fetchone() is not None:
839
            cursor.close()
840
            cnx.close()
841
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
842
                                   description='API.SENSOR_NAME_IS_ALREADY_IN_USE')
843
844
        add_values = (" INSERT INTO tbl_sensors "
845
                      "    (name, uuid, description) "
846
                      " VALUES (%s, %s, %s) ")
847
        cursor.execute(add_values, (name,
848
                                    str(uuid.uuid4()),
849
                                    description))
850
        new_id = cursor.lastrowid
851
        if 'points' in new_values.keys() and \
852
                new_values['points'] is not None and \
853
                len(new_values['points']) > 0:
854
            for point in new_values['points']:
855
                if 'id' in point and isinstance(point['id'], int):
856
                    cursor.execute(" SELECT name "
857
                                   " FROM tbl_points "
858
                                   " WHERE id = %s ", (point['id'],))
859
                    if cursor.fetchone() is None:
860
                        cursor.close()
861
                        cnx.close()
862
                        raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
863
                                               description='API.POINT_NOT_FOUND')
864
865
                    add_row = (" INSERT INTO tbl_sensors_points (sensor_id, point_id) "
866
                               " VALUES (%s, %s) ")
867
                    cursor.execute(add_row, (new_id, point['id'],))
868
                else:
869
                    raise falcon.HTTPError(status=falcon.HTTP_400, title='API.NOT_FOUND',
870
                                           description='API.INVALID_POINT_ID')
871
        cnx.commit()
872
        cursor.close()
873
        cnx.close()
874
875
        # Clear cache after importing sensor
876
        clear_sensor_cache()
877
878
        resp.status = falcon.HTTP_201
879
        resp.location = '/sensors/' + str(new_id)
880
881
882
class SensorClone:
883
884
    def __init__(self):
885
        pass
886
887
    @staticmethod
888
    def on_options(req, resp, id_):
889
        _ = req
890
        resp.status = falcon.HTTP_200
891
        _ = id_
892
893
    @staticmethod
894
    @user_logger
895
    def on_post(req, resp, id_):
896
        admin_control(req)
897
        if not id_.isdigit() or int(id_) <= 0:
898
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
899
                                   description='API.INVALID_SENSOR_ID')
900
901
        cnx = mysql.connector.connect(**config.myems_system_db)
902
        cursor = cnx.cursor()
903
904
        query = (" SELECT id, name, uuid, description "
905
                 " FROM tbl_sensors "
906
                 " WHERE id = %s ")
907
        cursor.execute(query, (id_,))
908
        row = cursor.fetchone()
909 View Code Duplication
        if row is None:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
910
            cursor.close()
911
            cnx.close()
912
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
913
                                   description='API.SENSOR_NOT_FOUND')
914
        else:
915
            meta_result = {"id": row[0],
916
                           "name": row[1],
917
                           "uuid": row[2],
918
                           "description": row[3],
919
                           "points": None}
920
921
            query = (" SELECT p.id, p.name "
922
                     " FROM tbl_points p, tbl_sensors_points sp "
923
                     " WHERE sp.sensor_id = %s AND p.id = sp.point_id "
924
                     " ORDER BY p.id ")
925
            cursor.execute(query, (id_,))
926
            rows = cursor.fetchall()
927
            result = list()
928
            if rows is not None and len(rows) > 0:
929
                for row in rows:
930
                    point_result = {"id": row[0], "name": row[1]}
931
                    result.append(point_result)
932
                meta_result['points'] = result
933
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
934
        if config.utc_offset[0] == '-':
935
            timezone_offset = -timezone_offset
936
        new_name = (str.strip(meta_result['name']) +
937
                    (datetime.utcnow() + timedelta(minutes=timezone_offset)).isoformat(sep='-', timespec='seconds'))
938
939
        add_values = (" INSERT INTO tbl_sensors "
940
                      "    (name, uuid, description) "
941
                      " VALUES (%s, %s, %s) ")
942
        cursor.execute(add_values, (new_name,
943
                                    str(uuid.uuid4()),
944
                                    meta_result['description']))
945
        new_id = cursor.lastrowid
946
        if meta_result['points'] is not None and len(meta_result['points']) > 0:
947
            for point in meta_result['points']:
948
                cursor.execute(" SELECT name "
949
                               " FROM tbl_points "
950
                               " WHERE id = %s ", (point['id'],))
951
                if cursor.fetchone() is None:
952
                    cursor.close()
953
                    cnx.close()
954
                    raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
955
                                           description='API.POINT_NOT_FOUND')
956
957
                add_row = (" INSERT INTO tbl_sensors_points (sensor_id, point_id) "
958
                           " VALUES (%s, %s) ")
959
                cursor.execute(add_row, (new_id, point['id'],))
960
961
        cnx.commit()
962
        cursor.close()
963
        cnx.close()
964
965
        # Clear cache after cloning sensor
966
        clear_sensor_cache()
967
968
        resp.status = falcon.HTTP_201
969
        resp.location = '/sensors/' + str(new_id)
970