Passed
Push — master ( f8b1cd...9ee003 )
by
unknown
10:23 queued 16s
created

core.protocol.clear_protocol_cache()   B

Complexity

Conditions 6

Size

Total Lines 38
Code Lines 23

Duplication

Lines 38
Ratio 100 %

Importance

Changes 0
Metric Value
eloc 23
dl 38
loc 38
rs 8.3946
c 0
b 0
f 0
cc 6
nop 1
1
from datetime import datetime, timedelta
2
import falcon
3
import mysql.connector
4
import simplejson as json
5
import redis
6
from core.useractivity import user_logger, admin_control, access_control, api_key_control
7
import config
8
9
10 View Code Duplication
def clear_protocol_cache(protocol_id=None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
11
    """
12
    Clear protocol-related cache after data modification
13
14
    Args:
15
        protocol_id: Protocol ID (optional, for specific protocol cache)
16
    """
17
    # Check if Redis is enabled
18
    if not config.redis.get('is_enabled', False):
19
        return
20
21
    redis_client = None
22
    try:
23
        redis_client = redis.Redis(
24
            host=config.redis['host'],
25
            port=config.redis['port'],
26
            password=config.redis['password'] if config.redis['password'] else None,
27
            db=config.redis['db'],
28
            decode_responses=True,
29
            socket_connect_timeout=2,
30
            socket_timeout=2
31
        )
32
        redis_client.ping()
33
34
        # Clear protocol list cache
35
        list_cache_key_pattern = 'protocol:list:*'
36
        matching_keys = redis_client.keys(list_cache_key_pattern)
37
        if matching_keys:
38
            redis_client.delete(*matching_keys)
39
40
        # Clear specific protocol item cache if protocol_id is provided
41
        if protocol_id:
42
            item_cache_key = f'protocol:item:{protocol_id}'
43
            redis_client.delete(item_cache_key)
44
45
    except Exception:
46
        # If cache clear fails, ignore and continue
47
        pass
48
49
50
class ProtocolCollection:
51
    """
52
    Protocol Collection Resource
53
54
    This class handles CRUD operations for protocol collection.
55
    It provides endpoints for listing all protocols and creating new protocols.
56
    Protocols define communication standards used by data sources.
57
    """
58
    def __init__(self):
59
        """Initialize ProtocolCollection"""
60
        pass
61
62
    @staticmethod
63
    def on_options(req, resp):
64
        """Handle OPTIONS requests for CORS preflight"""
65
        _ = req
66
        resp.status = falcon.HTTP_200
67
68
    @staticmethod
69
    def on_get(req, resp):
70
        """
71
        Handle GET requests to retrieve all protocols
72
73
        Returns a list of all protocols with their metadata including:
74
        - Protocol ID
75
        - Protocol name
76
        - Protocol code
77
78
        Args:
79
            req: Falcon request object
80
            resp: Falcon response object
81
        """
82
        # Check authentication - use API key if provided, otherwise use access control
83
        if 'API-KEY' not in req.headers or \
84
                not isinstance(req.headers['API-KEY'], str) or \
85
                len(str.strip(req.headers['API-KEY'])) == 0:
86
            access_control(req)
87
        else:
88
            api_key_control(req)
89
90
        # Redis cache key
91
        cache_key = 'protocol:list:all'
92
        cache_expire = 28800  # 8 hours in seconds (long-term cache)
93
94
        # Try to get from Redis cache (only if Redis is enabled)
95
        redis_client = None
96
        if config.redis.get('is_enabled', False):
97
            try:
98
                redis_client = redis.Redis(
99
                    host=config.redis['host'],
100
                    port=config.redis['port'],
101
                    password=config.redis['password'] if config.redis['password'] else None,
102
                    db=config.redis['db'],
103
                    decode_responses=True,
104
                    socket_connect_timeout=2,
105
                    socket_timeout=2
106
                )
107
                redis_client.ping()
108
                cached_result = redis_client.get(cache_key)
109
                if cached_result:
110
                    resp.text = cached_result
111
                    return
112
            except Exception:
113
                # If Redis connection fails, continue to database query
114
                pass
115
116
        # Cache miss or Redis error - query database
117
        cnx = mysql.connector.connect(**config.myems_system_db)
118
        cursor = cnx.cursor()
119
120
        query = (" SELECT id, name, code "
121
                 " FROM tbl_protocols "
122
                 " ORDER BY id ")
123
        cursor.execute(query)
124
        rows = cursor.fetchall()
125
        cursor.close()
126
        cnx.close()
127
128
        # Build result list
129
        result = list()
130
        if rows is not None and len(rows) > 0:
131
            for row in rows:
132
                meta_result = {"id": row[0],
133
                               "name": row[1],
134
                               "code": row[2]}
135
                result.append(meta_result)
136
137
        # Store result in Redis cache
138
        result_json = json.dumps(result)
139
        if redis_client:
140
            try:
141
                redis_client.setex(cache_key, cache_expire, result_json)
142
            except Exception:
143
                # If cache set fails, ignore and continue
144
                pass
145
146
        resp.text = result_json
147
148 View Code Duplication
    @staticmethod
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
149
    @user_logger
150
    def on_post(req, resp):
151
        """
152
        Handle POST requests to create a new protocol
153
154
        Creates a new protocol with the specified name and code.
155
        Validates that both name and code are unique.
156
157
        Args:
158
            req: Falcon request object containing protocol data:
159
                - name: Protocol name (required)
160
                - code: Protocol code (required)
161
            resp: Falcon response object
162
        """
163
        admin_control(req)
164
165
        # Read and parse request body
166
        try:
167
            raw_json = req.stream.read().decode('utf-8')
168
        except UnicodeDecodeError as ex:
169
            print("Failed to decode request")
170
            raise falcon.HTTPError(status=falcon.HTTP_400,
171
                                   title='API.BAD_REQUEST',
172
                                   description='API.INVALID_ENCODING')
173
        except Exception as ex:
174
            print("Unexpected error reading request stream")
175
            raise falcon.HTTPError(status=falcon.HTTP_400,
176
                                   title='API.BAD_REQUEST',
177
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
178
179
        new_values = json.loads(raw_json)
180
181
        # Validate protocol name
182
        if 'name' not in new_values['data'].keys() or \
183
                not isinstance(new_values['data']['name'], str) or \
184
                len(str.strip(new_values['data']['name'])) == 0:
185
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
186
                                   description='API.INVALID_PROTOCOL_NAME')
187
        name = str.strip(new_values['data']['name'])
188
189
        # Validate protocol code
190
        if 'code' not in new_values['data'].keys() or \
191
                not isinstance(new_values['data']['code'], str) or \
192
                len(str.strip(new_values['data']['code'])) == 0:
193
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
194
                                   description='API.INVALID_PROTOCOL_CODE')
195
        code = str.strip(new_values['data']['code'])
196
197
        # Connect to database
198
        cnx = mysql.connector.connect(**config.myems_system_db)
199
        cursor = cnx.cursor()
200
201
        # Check if protocol name already exists
202
        cursor.execute(" SELECT name "
203
                       " FROM tbl_protocols "
204
                       " WHERE name = %s ", (name,))
205
        if cursor.fetchone() is not None:
206
            cursor.close()
207
            cnx.close()
208
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
209
                                   description='API.PROTOCOL_NAME_IS_ALREADY_IN_USE')
210
211
        # Check if protocol code already exists
212
        cursor.execute(" SELECT code "
213
                       " FROM tbl_protocols "
214
                       " WHERE code = %s ", (code,))
215
        if cursor.fetchone() is not None:
216
            cursor.close()
217
            cnx.close()
218
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
219
                                   description='API.PROTOCOL_CODE_IS_ALREADY_IN_USE')
220
221
        # Insert new protocol
222
        add_row = (" INSERT INTO tbl_protocols "
223
                   "     (name, code) "
224
                   " VALUES (%s, %s) ")
225
226
        cursor.execute(add_row, (name,
227
                                 code))
228
        new_id = cursor.lastrowid
229
        cnx.commit()
230
        cursor.close()
231
        cnx.close()
232
233
        # Clear cache after creating new protocol
234
        clear_protocol_cache()
235
236
        resp.status = falcon.HTTP_201
237
        resp.location = '/protocols/' + str(new_id)
238
239
240
class ProtocolItem:
241
    """
242
    Protocol Item Resource
243
244
    This class handles individual protocol operations.
245
    It provides endpoints for retrieving, updating, and deleting specific protocols.
246
    """
247
248
    def __init__(self):
249
        """Initialize ProtocolItem"""
250
        pass
251
252
    @staticmethod
253
    def on_options(req, resp, id_):
254
        """Handle OPTIONS requests for CORS preflight"""
255
        _ = req
256
        resp.status = falcon.HTTP_200
257
        _ = id_
258
259
    @staticmethod
260
    def on_get(req, resp, id_):
261
        """
262
        Handle GET requests to retrieve a specific protocol
263
264
        Returns the protocol details for the specified ID.
265
266
        Args:
267
            req: Falcon request object
268
            resp: Falcon response object
269
            id_: Protocol ID to retrieve
270
        """
271
        # Check authentication - use API key if provided, otherwise use access control
272
        if 'API-KEY' not in req.headers or \
273
                not isinstance(req.headers['API-KEY'], str) or \
274
                len(str.strip(req.headers['API-KEY'])) == 0:
275
            access_control(req)
276
        else:
277
            api_key_control(req)
278
279
        # Validate protocol ID
280
        if not id_.isdigit() or int(id_) <= 0:
281
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
282
                                   description='API.INVALID_PROTOCOL_ID')
283
284
        # Redis cache key
285
        cache_key = f'protocol:item:{id_}'
286
        cache_expire = 28800  # 8 hours in seconds (long-term cache)
287
288
        # Try to get from Redis cache (only if Redis is enabled)
289
        redis_client = None
290
        if config.redis.get('is_enabled', False):
291
            try:
292
                redis_client = redis.Redis(
293
                    host=config.redis['host'],
294
                    port=config.redis['port'],
295
                    password=config.redis['password'] if config.redis['password'] else None,
296
                    db=config.redis['db'],
297
                    decode_responses=True,
298
                    socket_connect_timeout=2,
299
                    socket_timeout=2
300
                )
301
                redis_client.ping()
302
                cached_result = redis_client.get(cache_key)
303
                if cached_result:
304
                    resp.text = cached_result
305
                    return
306
            except Exception:
307
                # If Redis connection fails, continue to database query
308
                pass
309
310
        # Cache miss or Redis error - query database
311
        cnx = mysql.connector.connect(**config.myems_system_db)
312
        cursor = cnx.cursor()
313
314
        query = (" SELECT id, name, code "
315
                 " FROM tbl_protocols "
316
                 " WHERE id = %s ")
317
        cursor.execute(query, (id_,))
318
        row = cursor.fetchone()
319
        cursor.close()
320
        cnx.close()
321
322
        # Check if protocol exists
323
        if row is None:
324
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
325
                                   description='API.PROTOCOL_NOT_FOUND')
326
327
        # Build result
328
        result = {"id": row[0],
329
                  "name": row[1],
330
                  "code": row[2]}
331
332
        # Store result in Redis cache
333
        result_json = json.dumps(result)
334
        if redis_client:
335
            try:
336
                redis_client.setex(cache_key, cache_expire, result_json)
337
            except Exception:
338
                # If cache set fails, ignore and continue
339
                pass
340
341
        resp.text = result_json
342
343
    @staticmethod
344
    @user_logger
345
    def on_delete(req, resp, id_):
346
        """
347
        Handle DELETE requests to delete a specific protocol
348
349
        Deletes the protocol with the specified ID.
350
        Checks if the protocol is being used by any data sources before deletion.
351
352
        Args:
353
            req: Falcon request object
354
            resp: Falcon response object
355
            id_: Protocol ID to delete
356
        """
357
        admin_control(req)
358
359
        # Validate protocol ID
360
        if not id_.isdigit() or int(id_) <= 0:
361
            raise falcon.HTTPError(
362
                status=falcon.HTTP_400,
363
                title='API.BAD_REQUEST',
364
                description='API.INVALID_PROTOCOL_ID'
365
            )
366
367
        # Connect to database
368
        cnx = mysql.connector.connect(**config.myems_system_db)
369
        cursor = cnx.cursor()
370
371
        # Check if protocol exists
372
        cursor.execute("SELECT name,code FROM tbl_protocols WHERE id = %s", (id_,))
373
        row = cursor.fetchone()
374
        if row is None:
375
            cursor.close()
376
            cnx.close()
377
            raise falcon.HTTPError(
378
                status=falcon.HTTP_404,
379
                title='API.NOT_FOUND',
380
                description='API.PROTOCOL_NOT_FOUND'
381
            )
382
383
        # Check if this protocol is being used by any data sources
384
        code = row[1]
385
        cursor.execute(" SELECT name "
386
                       " FROM tbl_data_sources "
387
                       " WHERE protocol = %s "
388
                       " LIMIT 1 ",
389
                       (code,))
390
        if cursor.fetchone() is not None:
391
            cursor.close()
392
            cnx.close()
393
            raise falcon.HTTPError(status=falcon.HTTP_400,
394
                                   title='API.BAD_REQUEST',
395
                                   description='API.THERE_IS_RELATION_WITH_DATA_SOURCES')
396
397
        # Delete the protocol
398
        cursor.execute(" DELETE FROM tbl_protocols WHERE id = %s ", (id_,))
399
        cnx.commit()
400
401
        cursor.close()
402
        cnx.close()
403
404
        # Clear cache after deleting protocol
405
        clear_protocol_cache(protocol_id=id_)
406
407
        resp.status = falcon.HTTP_204
408
409
410
411 View Code Duplication
    @staticmethod
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
412
    @user_logger
413
    def on_put(req, resp, id_):
414
        """
415
        Handle PUT requests to update a specific protocol
416
417
        Updates the protocol with the specified ID.
418
        Validates that the new name and code are unique.
419
420
        Args:
421
            req: Falcon request object containing updated protocol data:
422
                - name: Updated protocol name (required)
423
                - code: Updated protocol code (required)
424
            resp: Falcon response object
425
            id_: Protocol ID to update
426
        """
427
        admin_control(req)
428
429
        # Read and parse request body
430
        try:
431
            raw_json = req.stream.read().decode('utf-8')
432
        except UnicodeDecodeError as ex:
433
            print("Failed to decode request")
434
            raise falcon.HTTPError(status=falcon.HTTP_400,
435
                                   title='API.BAD_REQUEST',
436
                                   description='API.INVALID_ENCODING')
437
        except Exception as ex:
438
            print("Unexpected error reading request stream")
439
            raise falcon.HTTPError(status=falcon.HTTP_400,
440
                                   title='API.BAD_REQUEST',
441
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
442
443
        # Validate protocol ID
444
        if not id_.isdigit() or int(id_) <= 0:
445
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
446
                                   description='API.INVALID_PROTOCOL_ID')
447
448
        new_values = json.loads(raw_json)
449
450
        # Validate protocol name
451
        if 'name' not in new_values['data'].keys() or \
452
                not isinstance(new_values['data']['name'], str) or \
453
                len(str.strip(new_values['data']['name'])) == 0:
454
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
455
                                   description='API.INVALID_PROTOCOL_NAME')
456
        name = str.strip(new_values['data']['name'])
457
458
        # Validate protocol code
459
        if 'code' not in new_values['data'].keys() or \
460
                not isinstance(new_values['data']['code'], str) or \
461
                len(str.strip(new_values['data']['code'])) == 0:
462
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
463
                                   description='API.INVALID_PROTOCOL_CODE')
464
        code = str.strip(new_values['data']['code'])
465
466
        # Connect to database
467
        cnx = mysql.connector.connect(**config.myems_system_db)
468
        cursor = cnx.cursor()
469
470
        # Check if protocol exists
471
        cursor.execute(" SELECT name "
472
                       " FROM tbl_protocols "
473
                       " WHERE id = %s ", (id_,))
474
        if cursor.fetchone() is None:
475
            cursor.close()
476
            cnx.close()
477
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
478
                                   description='API.PROTOCOL_NOT_FOUND')
479
480
        # Check if new name already exists (excluding current protocol)
481
        cursor.execute(" SELECT name "
482
                       " FROM tbl_protocols "
483
                       " WHERE name = %s AND id != %s ", (name, id_))
484
        if cursor.fetchone() is not None:
485
            cursor.close()
486
            cnx.close()
487
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
488
                                   description='API.PROTOCOL_NAME_IS_ALREADY_IN_USE')
489
490
        # Check if new code already exists (excluding current protocol)
491
        cursor.execute(" SELECT code "
492
                       " FROM tbl_protocols "
493
                       " WHERE code = %s AND id != %s ", (code, id_))
494
        if cursor.fetchone() is not None:
495
            cursor.close()
496
            cnx.close()
497
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
498
                                   description='API.PROTOCOL_CODE_IS_ALREADY_IN_USE')
499
500
        # Update the protocol
501
        update_row = (" UPDATE tbl_protocols "
502
                      " SET name = %s, code = %s "
503
                      " WHERE id = %s ")
504
        cursor.execute(update_row, (name,
505
                                    code,
506
                                    id_,))
507
        cnx.commit()
508
509
        cursor.close()
510
        cnx.close()
511
512
        # Clear cache after updating protocol
513
        clear_protocol_cache(protocol_id=id_)
514
515
        resp.status = falcon.HTTP_200
516
517
518
519