NonWorkingDayItem.on_options()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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