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

core.contact.clear_contact_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
import re
2
import uuid
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_contact_cache(contact_id=None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
12
    """
13
    Clear contact-related cache after data modification
14
15
    Args:
16
        contact_id: Contact ID (optional, for specific contact 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 contact list cache (all search query variations)
36
        list_cache_key_pattern = 'contact: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 contact item cache if contact_id is provided
42
        if contact_id:
43
            item_cache_key = f'contact:item:{contact_id}'
44
            redis_client.delete(item_cache_key)
45
46
    except Exception:
47
        # If cache clear fails, ignore and continue
48
        pass
49
50
51
class ContactCollection:
52
    """
53
    Contact Collection Resource
54
55
    This class handles CRUD operations for contact collection.
56
    It provides endpoints for listing all contacts and creating new contacts.
57
    Contacts represent individuals or organizations in the energy management system.
58
    """
59
    def __init__(self):
60
        """Initialize ContactCollection"""
61
        pass
62
63
    @staticmethod
64
    def on_options(req, resp):
65
        """Handle OPTIONS requests for CORS preflight"""
66
        _ = req
67
        resp.status = falcon.HTTP_200
68
69
    @staticmethod
70
    def on_get(req, resp):
71
        if 'API-KEY' not in req.headers or \
72
                not isinstance(req.headers['API-KEY'], str) or \
73
                len(str.strip(req.headers['API-KEY'])) == 0:
74
            access_control(req)
75
        else:
76
            api_key_control(req)
77
        search_query = req.get_param('q', default=None)
78
        if search_query is not None:
79
            search_query = search_query.strip()
80
        else:
81
            search_query = ''
82
83
        # Redis cache key
84
        cache_key = f'contact:list:{search_query}'
85
        cache_expire = 28800  # 8 hours in seconds (long-term cache)
86
87
        # Try to get from Redis cache (only if Redis is enabled)
88
        redis_client = None
89
        if config.redis.get('is_enabled', False):
90
            try:
91
                redis_client = redis.Redis(
92
                    host=config.redis['host'],
93
                    port=config.redis['port'],
94
                    password=config.redis['password'] if config.redis['password'] else None,
95
                    db=config.redis['db'],
96
                    decode_responses=True,
97
                    socket_connect_timeout=2,
98
                    socket_timeout=2
99
                )
100
                redis_client.ping()
101
                cached_result = redis_client.get(cache_key)
102
                if cached_result:
103
                    resp.text = cached_result
104
                    return
105
            except Exception:
106
                # If Redis connection fails, continue to database query
107
                pass
108
109
        # Cache miss or Redis error - query database
110
        cnx = mysql.connector.connect(**config.myems_system_db)
111
        cursor = cnx.cursor()
112
113
        query = (" SELECT id, name, uuid, "
114
                 "        email, phone, description "
115
                 " FROM tbl_contacts " )
116
117
        params=[]
118
        if search_query:
119
            query += " WHERE name LIKE %s OR  description LIKE %s "
120
            params = [f'%{search_query}%', f'%{search_query}%']
121
        query +=  " ORDER BY name "
122
123
        cursor.execute(query,params)
124
        rows = cursor.fetchall()
125
        cursor.close()
126
        cnx.close()
127
128
        result = list()
129
        if rows is not None and len(rows) > 0:
130
            for row in rows:
131
                meta_result = {"id": row[0],
132
                               "name": row[1],
133
                               "uuid": row[2],
134
                               "email": row[3],
135
                               "phone": row[4],
136
                               "description": row[5]}
137
                result.append(meta_result)
138
139
        # Store result in Redis cache
140
        result_json = json.dumps(result)
141
        if redis_client:
142
            try:
143
                redis_client.setex(cache_key, cache_expire, result_json)
144
            except Exception:
145
                # If cache set fails, ignore and continue
146
                pass
147
148
        resp.text = result_json
149
150
    @staticmethod
151
    @user_logger
152
    def on_post(req, resp):
153
        """Handles POST requests"""
154
        admin_control(req)
155
        try:
156
            raw_json = req.stream.read().decode('utf-8')
157
        except UnicodeDecodeError as ex:
158
            print("Failed to decode request")
159
            raise falcon.HTTPError(status=falcon.HTTP_400,
160
                                   title='API.BAD_REQUEST',
161
                                   description='API.INVALID_ENCODING')
162
        except Exception as ex:
163
            print("Unexpected error reading request stream")
164
            raise falcon.HTTPError(status=falcon.HTTP_400,
165
                                   title='API.BAD_REQUEST',
166
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
167
168
        new_values = json.loads(raw_json)
169
170
        if 'name' not in new_values['data'].keys() or \
171
                not isinstance(new_values['data']['name'], str) or \
172
                len(str.strip(new_values['data']['name'])) == 0:
173
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
174
                                   description='API.INVALID_USER_NAME')
175
        name = str.strip(new_values['data']['name'])
176
177
        if 'email' not in new_values['data'].keys() or \
178
                not isinstance(new_values['data']['email'], str) or \
179
                len(str.strip(new_values['data']['email'])) == 0:
180
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
181
                                   description='API.INVALID_EMAIL')
182
        email = str.lower(str.strip(new_values['data']['email']))
183
184
        match = re.match(r'^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', email)
185
        if match is None:
186
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
187
                                   description='API.INVALID_EMAIL')
188
189
        if 'phone' not in new_values['data'].keys() or \
190
                not isinstance(new_values['data']['phone'], str) or \
191
                len(str.strip(new_values['data']['phone'])) == 0:
192
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
193
                                   description='API.INVALID_USER_PHONE')
194
        phone = str.strip(new_values['data']['phone'])
195
196
        if 'description' in new_values['data'].keys() and \
197
                new_values['data']['description'] is not None and \
198
                len(str(new_values['data']['description'])) > 0:
199
            description = str.strip(new_values['data']['description'])
200
        else:
201
            description = None
202
203
        cnx = mysql.connector.connect(**config.myems_system_db)
204
        cursor = cnx.cursor()
205
206
        cursor.execute(" SELECT name "
207
                       " FROM tbl_contacts "
208
                       " WHERE name = %s ", (name,))
209
        if cursor.fetchone() is not None:
210
            cursor.close()
211
            cnx.close()
212
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
213
                                   description='API.CONTACT_NAME_IS_ALREADY_IN_USE')
214
215
        add_row = (" INSERT INTO tbl_contacts "
216
                   "     (name, uuid, email, phone, description) "
217
                   " VALUES (%s, %s, %s, %s, %s) ")
218
219
        cursor.execute(add_row, (name,
220
                                 str(uuid.uuid4()),
221
                                 email,
222
                                 phone,
223
                                 description))
224
        new_id = cursor.lastrowid
225
        cnx.commit()
226
        cursor.close()
227
        cnx.close()
228
229
        # Clear cache after creating new contact
230
        clear_contact_cache()
231
232
        resp.status = falcon.HTTP_201
233
        resp.location = '/contacts/' + str(new_id)
234
235
236
class ContactItem:
237
    def __init__(self):
238
        pass
239
240
    @staticmethod
241
    def on_options(req, resp, id_):
242
        _ = id_
243
        _ = req
244
        resp.status = falcon.HTTP_200
245
246
    @staticmethod
247
    def on_get(req, resp, id_):
248
        if 'API-KEY' not in req.headers or \
249
                not isinstance(req.headers['API-KEY'], str) or \
250
                len(str.strip(req.headers['API-KEY'])) == 0:
251
            access_control(req)
252
        else:
253
            api_key_control(req)
254
        if not id_.isdigit() or int(id_) <= 0:
255
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
256
                                   description='API.INVALID_CONTACT_ID')
257
258
        # Redis cache key
259
        cache_key = f'contact:item:{id_}'
260
        cache_expire = 28800  # 8 hours in seconds (long-term cache)
261
262
        # Try to get from Redis cache (only if Redis is enabled)
263
        redis_client = None
264
        if config.redis.get('is_enabled', False):
265
            try:
266
                redis_client = redis.Redis(
267
                    host=config.redis['host'],
268
                    port=config.redis['port'],
269
                    password=config.redis['password'] if config.redis['password'] else None,
270
                    db=config.redis['db'],
271
                    decode_responses=True,
272
                    socket_connect_timeout=2,
273
                    socket_timeout=2
274
                )
275
                redis_client.ping()
276
                cached_result = redis_client.get(cache_key)
277
                if cached_result:
278
                    resp.text = cached_result
279
                    return
280
            except Exception:
281
                # If Redis connection fails, continue to database query
282
                pass
283
284
        # Cache miss or Redis error - query database
285
        cnx = mysql.connector.connect(**config.myems_system_db)
286
        cursor = cnx.cursor()
287
288
        query = (" SELECT id, name, uuid, email, phone, description "
289
                 " FROM tbl_contacts "
290
                 " WHERE id = %s ")
291
        cursor.execute(query, (id_,))
292
        row = cursor.fetchone()
293
        cursor.close()
294
        cnx.close()
295
296
        if row is None:
297
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
298
                                   description='API.CONTACT_NOT_FOUND')
299
300
        result = {"id": row[0],
301
                  "name": row[1],
302
                  "uuid": row[2],
303
                  "email": row[3],
304
                  "phone": row[4],
305
                  "description": row[5]}
306
307
        # Store result in Redis cache
308
        result_json = json.dumps(result)
309
        if redis_client:
310
            try:
311
                redis_client.setex(cache_key, cache_expire, result_json)
312
            except Exception:
313
                # If cache set fails, ignore and continue
314
                pass
315
316
        resp.text = result_json
317
318
    @staticmethod
319
    @user_logger
320
    def on_delete(req, resp, id_):
321
        admin_control(req)
322
        if not id_.isdigit() or int(id_) <= 0:
323
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
324
                                   description='API.INVALID_CONTACT_ID')
325
326
        cnx = mysql.connector.connect(**config.myems_system_db)
327
        cursor = cnx.cursor()
328
329
        cursor.execute(" SELECT name "
330
                       " FROM tbl_contacts "
331
                       " WHERE id = %s ", (id_,))
332
        if cursor.fetchone() is None:
333
            cursor.close()
334
            cnx.close()
335
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
336
                                   description='API.CONTACT_NOT_FOUND')
337
338
        # check relation with shopfloors
339
        cursor.execute(" SELECT id "
340
                       " FROM tbl_shopfloors "
341
                       " WHERE contact_id = %s ", (id_,))
342
        rows_shopfloors = cursor.fetchall()
343
        if rows_shopfloors is not None and len(rows_shopfloors) > 0:
344
            cursor.close()
345
            cnx.close()
346
            raise falcon.HTTPError(status=falcon.HTTP_400,
347
                                   title='API.BAD_REQUEST',
348
                                   description='API.THERE_IS_RELATION_WITH_SHOPFLOORS')
349
350
        # check relation with spaces
351
        cursor.execute(" SELECT id "
352
                       " FROM tbl_spaces "
353
                       " WHERE contact_id = %s ", (id_,))
354
        rows_spaces = cursor.fetchall()
355
        if rows_spaces is not None and len(rows_spaces) > 0:
356
            cursor.close()
357
            cnx.close()
358
            raise falcon.HTTPError(status=falcon.HTTP_400,
359
                                   title='API.BAD_REQUEST',
360
                                   description='API.THERE_IS_RELATION_WITH_SPACES')
361
362
        # check relation with stores
363
        cursor.execute(" SELECT id "
364
                       " FROM tbl_stores "
365
                       " WHERE contact_id = %s ", (id_,))
366
        rows_stores = cursor.fetchall()
367
        if rows_stores is not None and len(rows_stores) > 0:
368
            cursor.close()
369
            cnx.close()
370
            raise falcon.HTTPError(status=falcon.HTTP_400,
371
                                   title='API.BAD_REQUEST',
372
                                   description='API.THERE_IS_RELATION_WITH_STORES')
373
374
        # check relation with tenants
375
        cursor.execute(" SELECT id "
376
                       " FROM tbl_tenants "
377
                       " WHERE contact_id = %s ", (id_,))
378
        rows_tenants = cursor.fetchall()
379
        if rows_tenants is not None and len(rows_tenants) > 0:
380
            cursor.close()
381
            cnx.close()
382
            raise falcon.HTTPError(status=falcon.HTTP_400,
383
                                   title='API.BAD_REQUEST',
384
                                   description='API.THERE_IS_RELATION_WITH_TENANTS')
385
386
        # check relation with charging_stations
387
        cursor.execute(" SELECT id "
388
                       " FROM tbl_charging_stations "
389
                       " WHERE contact_id = %s ", (id_,))
390
        rows_charging_stations = cursor.fetchall()
391
        if rows_charging_stations is not None and len(rows_charging_stations) > 0:
392
            cursor.close()
393
            cnx.close()
394
            raise falcon.HTTPError(status=falcon.HTTP_400,
395
                                   title='API.BAD_REQUEST',
396
                                   description='API.THERE_IS_RELATION_WITH_CHARGING_STATIONS')
397
398
        # check relation with energy_storage_containers
399
        cursor.execute(" SELECT id "
400
                       " FROM tbl_energy_storage_containers "
401
                       " WHERE contact_id = %s ", (id_,))
402
        rows_energy_storage_containers = cursor.fetchall()
403
        if rows_energy_storage_containers is not None and len(rows_energy_storage_containers) > 0:
404
            cursor.close()
405
            cnx.close()
406
            raise falcon.HTTPError(status=falcon.HTTP_400,
407
                                   title='API.BAD_REQUEST',
408
                                   description='API.THERE_IS_RELATION_WITH_ENERGY_STORAGE_CONTAINERS')
409
410
411
        # check relation with energy_storage_power_stations
412
        cursor.execute(" SELECT id "
413
                       " FROM tbl_energy_storage_power_stations "
414
                       " WHERE contact_id = %s ", (id_,))
415
        rows_energy_storage_power_stations = cursor.fetchall()
416
        if rows_energy_storage_power_stations is not None and len(rows_energy_storage_power_stations) > 0:
417
            cursor.close()
418
            cnx.close()
419
            raise falcon.HTTPError(status=falcon.HTTP_400,
420
                                   title='API.BAD_REQUEST',
421
                                   description='API.THERE_IS_RELATION_WITH_ENERGY_STORAGE_POWER_STATIONS')
422
423
        # check relation with microgrids
424
        cursor.execute(" SELECT id "
425
                       " FROM tbl_microgrids "
426
                       " WHERE contact_id = %s ", (id_,))
427
        rows_microgrids = cursor.fetchall()
428
        if rows_microgrids is not None and len(rows_microgrids) > 0:
429
            cursor.close()
430
            cnx.close()
431
            raise falcon.HTTPError(status=falcon.HTTP_400,
432
                                   title='API.BAD_REQUEST',
433
                                   description='API.THERE_IS_RELATION_WITH_MICROGRIDS')
434
435
        # check relation with photovoltaic_power_stations
436
        cursor.execute(" SELECT id "
437
                       " FROM tbl_photovoltaic_power_stations "
438
                       " WHERE contact_id = %s ", (id_,))
439
        rows_photovoltaic_power_stations = cursor.fetchall()
440
        if rows_photovoltaic_power_stations  is not None and len(rows_photovoltaic_power_stations) > 0:
441
            cursor.close()
442
            cnx.close()
443
            raise falcon.HTTPError(status=falcon.HTTP_400,
444
                                   title='API.BAD_REQUEST',
445
                                   description='API.THERE_IS_RELATION_WITH_PHOTOVOLTAIC_POWER_STATIONS')
446
447
        #check relation with wind_farms
448
        cursor.execute(" SELECT id "
449
                       " FROM tbl_wind_farms "
450
                       " WHERE contact_id = %s ", (id_,))
451
        rows_wind_farms = cursor.fetchall()
452
        if rows_wind_farms is not None and len(rows_wind_farms) > 0:
453
            cursor.close()
454
            cnx.close()
455
            raise falcon.HTTPError(status=falcon.HTTP_400,
456
                                   title='API.BAD_REQUEST',
457
                                   description='API.THERE_IS_RELATION_WITH_WIND_FARMS')
458
459
460
        cursor.execute(" DELETE FROM tbl_contacts WHERE id = %s ", (id_,))
461
        cnx.commit()
462
463
        cursor.close()
464
        cnx.close()
465
466
        # Clear cache after deleting contact
467
        clear_contact_cache(contact_id=id_)
468
469
        resp.status = falcon.HTTP_204
470
471
    @staticmethod
472
    @user_logger
473
    def on_put(req, resp, id_):
474
        """Handles PUT requests"""
475
        admin_control(req)
476
        try:
477
            raw_json = req.stream.read().decode('utf-8')
478
        except UnicodeDecodeError as ex:
479
            print("Failed to decode request")
480
            raise falcon.HTTPError(status=falcon.HTTP_400,
481
                                   title='API.BAD_REQUEST',
482
                                   description='API.INVALID_ENCODING')
483
        except Exception as ex:
484
            print("Unexpected error reading request stream")
485
            raise falcon.HTTPError(status=falcon.HTTP_400,
486
                                   title='API.BAD_REQUEST',
487
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
488
489
        if not id_.isdigit() or int(id_) <= 0:
490
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
491
                                   description='API.INVALID_CONTACT_ID')
492
493
        new_values = json.loads(raw_json)
494
495
        if 'name' not in new_values['data'].keys() or \
496
                not isinstance(new_values['data']['name'], str) or \
497
                len(str.strip(new_values['data']['name'])) == 0:
498
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
499
                                   description='API.INVALID_CONTACT_NAME')
500
        name = str.strip(new_values['data']['name'])
501
502
        if 'email' not in new_values['data'].keys() or \
503
                not isinstance(new_values['data']['email'], str) or \
504
                len(str.strip(new_values['data']['email'])) == 0:
505
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
506
                                   description='API.INVALID_EMAIL')
507
        email = str.lower(str.strip(new_values['data']['email']))
508
509
        match = re.match(r'^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', email)
510
        if match is None:
511
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
512
                                   description='API.INVALID_EMAIL')
513
514
        if 'phone' not in new_values['data'].keys() or \
515
                not isinstance(new_values['data']['phone'], str) or \
516
                len(str.strip(new_values['data']['phone'])) == 0:
517
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
518
                                   description='API.INVALID_USER_PHONE')
519
        phone = str.strip(new_values['data']['phone'])
520
521
        if 'description' in new_values['data'].keys() and \
522
                new_values['data']['description'] is not None and \
523
                len(str(new_values['data']['description'])) > 0:
524
            description = str.strip(new_values['data']['description'])
525
        else:
526
            description = None
527
528
        cnx = mysql.connector.connect(**config.myems_system_db)
529
        cursor = cnx.cursor()
530
531
        cursor.execute(" SELECT name "
532
                       " FROM tbl_contacts "
533
                       " WHERE id = %s ", (id_,))
534
        if cursor.fetchone() is None:
535
            cursor.close()
536
            cnx.close()
537
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
538
                                   description='API.CONTACT_NOT_FOUND')
539
540
        cursor.execute(" SELECT name "
541
                       " FROM tbl_contacts "
542
                       " WHERE name = %s AND id != %s ", (name, id_))
543
        if cursor.fetchone() is not None:
544
            cursor.close()
545
            cnx.close()
546
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
547
                                   description='API.CONTACT_NAME_IS_ALREADY_IN_USE')
548
549
        update_row = (" UPDATE tbl_contacts "
550
                      " SET name = %s, email = %s, "
551
                      "     phone = %s, description = %s "
552
                      " WHERE id = %s ")
553
        cursor.execute(update_row, (name,
554
                                    email,
555
                                    phone,
556
                                    description,
557
                                    id_,))
558
        cnx.commit()
559
560
        cursor.close()
561
        cnx.close()
562
563
        # Clear cache after updating contact
564
        clear_contact_cache(contact_id=id_)
565
566
        resp.status = falcon.HTTP_200
567
568