diff --git a/app/main.py b/app/main.py index 853b339..c8f6a91 100644 --- a/app/main.py +++ b/app/main.py @@ -18,7 +18,7 @@ from app.lib_general import log, logging from app.log import log # Import the routers here first: -from app.routers import api_crud, api, importing, account, address, archive, archive_content, contact, cont_edu_cert, cont_edu_cert_person, event, event_badge, event_badge_template, event_exhibit, event_file, event_importing, event_location, event_person, event_person_detail, event_person_tracking, event_presentation, event_presenter, event_registration, event_session, flask_cfg, hosted_file, journal, journal_entry, log_client_viewing, lookup, membership_cfg, membership_group, membership_group_person, membership_person, membership_person_profile, membership_type, membership_type_person, order, order_line, order_cart, organization, page, person, person_user, post, post_comment, product, site, site_domain, user, websockets#, e_impexium +from app.routers import api_crud, api, importing, account, activity_log, address, archive, archive_content, contact, cont_edu_cert, cont_edu_cert_person, event, event_badge, event_badge_template, event_exhibit, event_file, event_importing, event_location, event_person, event_person_detail, event_person_tracking, event_presentation, event_presenter, event_registration, event_session, flask_cfg, hosted_file, journal, journal_entry, log_client_viewing, lookup, membership_cfg, membership_group, membership_group_person, membership_person, membership_person_profile, membership_type, membership_type_person, order, order_line, order_cart, organization, page, person, person_user, post, post_comment, product, site, site_domain, user, websockets#, e_impexium from app.db_sql import db @@ -83,6 +83,11 @@ app.include_router( prefix='/account', tags=['Account'], ) +app.include_router( + activity_log.router, + prefix='/activity_log', + tags=['Activity Log'], +) app.include_router( address.router, prefix='/address', diff --git a/app/methods/activity_log_methods.py b/app/methods/activity_log_methods.py new file mode 100644 index 0000000..4e437d6 --- /dev/null +++ b/app/methods/activity_log_methods.py @@ -0,0 +1,97 @@ +from __future__ import annotations +import datetime + +from typing import Dict, List, Optional, Set, Union +from pydantic import BaseModel, EmailStr, Field, PrivateAttr, ValidationError, validator + +from app.db_sql import redis_lookup_id_random, sql_insert, sql_insert_or_update, sql_select, sql_update +from app.lib_general import log, logging + + +from app.models.common_field_schema import default_num_bytes +from app.models.activity_log_models import Activity_Log_Base + + +# ### BEGIN ### API Activity Log Methods ### load_activity_log_obj() ### +def load_activity_log_obj( + activity_log_id: int|str, + limit: int = 10000, + by_alias: bool = True, + exclude_unset: bool = True, + model_as_dict: bool = False, + ) -> Activity_Log_Base|bool: + log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + if activity_log_id := redis_lookup_id_random(record_id_random=activity_log_id, table_name='activity_log'): pass + else: return False + + if activity_log_rec := sql_select(table_name='v_activity_log', record_id=activity_log_id): pass + else: return False + + try: + activity_log_obj = Activity_Log_Base(**activity_log_rec) + log.debug(activity_log_obj) + except ValidationError as e: + log.error(e.json()) + + if model_as_dict: + return activity_log_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member + else: + return activity_log_obj +# ### END ### API Activity Log Methods ### load_activity_log_obj() ### + + +# ### BEGIN ### API Activity Log Methods ### get_activity_log_rec_list() ### +def get_activity_log_rec_list( + account_id: str, + from_datetime: datetime.datetime = None, + to_datetime: datetime.datetime = None, + limit: int = 1000, + enabled: str = 'enabled', # enabled, disabled, all + ) -> list|bool: + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass + else: return False + data = {} + data['account_id'] = account_id + sql_account_id = f'`activity_log`.account_id = :account_id' + + # if enabled in ['enabled', 'disabled', 'all']: + # if enabled == 'enabled': + # data['enable'] = True + # sql_enabled = f'AND `activity_log`.enable = :enable' + # elif enabled == 'disabled': + # data['enable'] = False + # sql_enabled = f'AND `activity_log`.enable = :enable' + # elif enabled == 'all': + # sql_enabled = '' + sql_enabled = '' + + if limit: + data['limit'] = limit + sql_limit = f'LIMIT :limit' + else: + sql_limit = '' + + sql = f""" + SELECT `activity_log`.id AS 'activity_log_id', `activity_log`.id_random AS 'activity_log_id_random' + FROM `activity_log` AS `activity_log` + WHERE + {sql_account_id} + {sql_enabled} + ORDER BY `activity_log`.created_on DESC, `activity_log`.updated_on DESC + {sql_limit}; + """ + + if activity_log_rec_li_result := sql_select(data=data, sql=sql, as_list=True): + activity_log_rec_li = activity_log_rec_li_result + else: + activity_log_rec_li = [] + log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(activity_log_rec_li_result) + + return activity_log_rec_li +# ### END ### API Activity Log Methods ### get_activity_log_rec_list() ### diff --git a/app/models/activity_log_models.py b/app/models/activity_log_models.py new file mode 100644 index 0000000..6619b85 --- /dev/null +++ b/app/models/activity_log_models.py @@ -0,0 +1,128 @@ +from __future__ import annotations +import datetime, hashlib, logging, os, pytz, redis, secrets + +from typing import Dict, List, Optional, Set, Union +from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator + +from app.db_sql import redis_lookup_id_random +from app.lib_general import log, logging + +from app.models.common_field_schema import base_fields, default_num_bytes + + +class Activity_Log_Base(BaseModel): + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + id_random: Optional[str] = Field( + **base_fields['activity_log_id_random'], + alias = 'activity_log_id_random', + default_factory = lambda:secrets.token_urlsafe(default_num_bytes), + ) + id: Optional[int] = Field( + alias = 'activity_log_id' + ) + + account_id_random: Optional[str] + account_id: Optional[int] + + person_id_random: Optional[str] + person_id: Optional[int] + + user_id_random: Optional[str] + user_id: Optional[int] + + external_client_id: Optional[str] + + google_ga: Optional[str] + google_gid: Optional[str] + + name: Optional[str] + description: Optional[str] + + source: Optional[str] + url_root: Optional[str] + url_full_path: Optional[str] + url_params: Optional[str] + + object_type: Optional[str] + object_id_random: Optional[str] + object_id: Optional[int] + + action: Optional[str] + action_with: Optional[str] + action_on_type: Optional[str] + action_on_id_random: Optional[str] + action_on_id: Optional[int] + action_on_code: Optional[str] + action_data: Optional[str] + + code: Optional[str] + + type_id: Optional[int] + type_name: Optional[str] + + details: Optional[str] + + # For now just using string instead of Json Pydantic data type + other_json: Optional[str] # When getting the dict version for SQL this should be a string. + meta_json: Optional[str] # When getting the dict version for SQL this should be a string. + + notes: Optional[str] + created_on: Optional[datetime.datetime] = None + updated_on: Optional[datetime.datetime] = None + + _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) + + #@validator('activity_log_id_random', always=True) + def activity_log_id_random_copy(cls, v, values, **kwargs): + log.setLevel(logging.WARNING) + log.debug(locals()) + + if id_random := values.get('id_random'): + return id_random + return None + + @validator('id', always=True) + def activity_log_id_lookup(cls, v, values, **kwargs): + log.setLevel(logging.WARNING) + log.debug(locals()) + + if values['id_random']: + log.debug(values['id_random']) + return redis_lookup_id_random(record_id_random=values['id_random'], table_name='activity_log') + return None + + @validator('account_id', always=True) + def account_id_lookup(cls, v, values, **kwargs): + log.setLevel(logging.WARNING) + log.debug(locals()) + + if values['account_id_random']: + return redis_lookup_id_random(record_id_random=values['account_id_random'], table_name='account') + return None + + @validator('person_id', always=True) + def person_id_lookup(cls, v, values, **kwargs): + log.setLevel(logging.WARNING) + log.debug(locals()) + + if values['person_id_random']: + return redis_lookup_id_random(record_id_random=values['person_id_random'], table_name='person') + return None + + @validator('user_id', always=True) + def user_id_lookup(cls, v, values, **kwargs): + log.setLevel(logging.WARNING) + log.debug(locals()) + + if values['user_id_random']: + return redis_lookup_id_random(record_id_random=values['user_id_random'], table_name='user') + return None + + class Config: + underscore_attrs_are_private = True + allow_population_by_field_name = True + fields = base_fields + +#Activity_Log_Base.update_forward_refs() diff --git a/app/models/common_field_schema.py b/app/models/common_field_schema.py index c38258e..effadd9 100644 --- a/app/models/common_field_schema.py +++ b/app/models/common_field_schema.py @@ -24,6 +24,7 @@ base_fields = {} base_fields['obj_id_random'] = xxx_id_random_field_schema # General or generic object_id_random base_fields['account_id_random'] = xxx_id_random_field_schema base_fields['account_cfg_id_random'] = xxx_id_random_field_schema +base_fields['activity_log_id_random'] = xxx_id_random_field_schema base_fields['address_id_random'] = xxx_id_random_field_schema base_fields['archive_id_random'] = xxx_id_random_field_schema base_fields['archive_content_id_random'] = xxx_id_random_field_schema diff --git a/app/routers/activity_log.py b/app/routers/activity_log.py new file mode 100644 index 0000000..d96ef1f --- /dev/null +++ b/app/routers/activity_log.py @@ -0,0 +1,180 @@ +import datetime +#from datetime import datetime, time, timedelta +from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query, Response, status +from pydantic import BaseModel, EmailStr, Field +from typing import Dict, List, Optional, Set, Union + +from app.lib_general import log, logging +#from ..log import * +from app.config import settings +from app.db_sql import * + +from app.routers.api_crud import delete_obj_template, get_obj_template, get_obj_li_template, patch_obj_template, post_obj_template + +from app.methods.activity_log_methods import get_activity_log_rec_list, load_activity_log_obj + +from app.models.activity_log_models import Activity_Log_Base +from app.models.response_models import * + + +router = APIRouter() + + +@router.post('/activity_log', response_model=Resp_Body_Base) +async def post_activity_log_obj( + activity_log_obj: Activity_Log_Base, + x_account_id: str = Header(...), + return_obj: Optional[bool] = True, + by_alias: Optional[bool] = True, + exclude_unset: Optional[bool] = True, + response: Response = Response, + ): + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + obj_type = 'activity_log' + activity_log_obj_data_dict = activity_log_obj.dict(by_alias=False, exclude_unset=True) + result = post_obj_template( + obj_type = obj_type, + data = activity_log_obj_data_dict, + return_obj = True, + by_alias = True, + exclude_unset = True, + ) + return result + + +@router.patch('/activity_log/{activity_log_id}', response_model=Resp_Body_Base) +async def patch_activity_log_obj( + activity_log_obj: Activity_Log_Base, + activity_log_id: str = Query(..., min_length=1, max_length=22), + x_account_id: Optional[str] = Header(..., ), + return_obj: Optional[bool] = True, + by_alias: Optional[bool] = True, + exclude_unset: Optional[bool] = True, + response: Response = Response, + ): + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + obj_type = 'activity_log' + activity_log_obj_data_dict = activity_log_obj.dict(by_alias=False, exclude_unset=True) + activity_log_obj_data_dict['id'] = redis_lookup_id_random(record_id_random=activity_log_id, table_name=obj_type) + activity_log_obj_data_dict['id_random'] = activity_log_id + result = patch_obj_template( + obj_type = obj_type, + data = activity_log_obj_data_dict, + obj_id = activity_log_id, + return_obj = True, + by_alias = True, + exclude_unset = True, + ) + return result + + +@router.get('/activity_log/list', response_model=Resp_Body_Base) +async def get_activity_log_obj_li( + for_obj_type: Optional[str] = Query(None, min_length=2, max_length=50), + for_obj_id: Optional[str] = Query(None, min_length=1, max_length=22), + x_account_id: str = Header(...), + by_alias: Optional[bool] = True, + exclude_unset: Optional[bool] = True, + response: Response = Response, + ): + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + obj_type = 'activity_log' + result = get_obj_li_template( + obj_type=obj_type, + for_obj_type=for_obj_type, + for_obj_id=for_obj_id, + by_alias=True, + exclude_unset=True, + ) + return result + + +@router.get('/activity_log/{obj_id}', response_model=Resp_Body_Base) +async def get_activity_log_obj( + obj_id: str = Query(..., min_length=1, max_length=22), + x_account_id: str = Header(...), + by_alias: Optional[bool] = True, + exclude_unset: Optional[bool] = True, + response: Response = Response, + ): + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + obj_type = 'activity_log' + result = get_obj_template( + obj_type=obj_type, + obj_id=obj_id, + by_alias=True, + exclude_unset=True, + ) + return result + + +# ### BEGIN ### API Activity Log ### get_account_obj_activity_log_list() ### +# Updated 2021-09-21 +@router.get('/account/{account_id}/activity_log/list', response_model=Resp_Body_Base) +async def get_account_obj_activity_log_list( + account_id: str = Query(..., min_length=11, max_length=22), + limit: int = 500, # For now this covers any included objects or object lists + enabled: str = 'enabled', # For now this covers any included objects or object lists + x_account_id: str = Header(...), + by_alias: Optional[bool] = True, + exclude_unset: Optional[bool] = True, + response: Response = Response, + ): + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass + else: return mk_resp(data=None, status_code=404, response=response) + + # Updated 2021-09-21 + if activity_log_rec_list_result := get_activity_log_rec_list( + account_id = account_id, + enabled = enabled, + limit = limit, + ): + activity_log_result_list = [] + for activity_log_rec in activity_log_rec_list_result: + if load_activity_log_result := load_activity_log_obj( + activity_log_id = activity_log_rec.get('activity_log_id', None), + enabled = enabled, + limit = limit, + by_alias = by_alias, + exclude_unset = exclude_unset, + # model_as_dict = model_as_dict, + ): + activity_log_result_list.append(load_activity_log_result) + else: + activity_log_result_list.append(None) + response_data = activity_log_result_list + elif isinstance(activity_log_rec_list_result, list): + return mk_resp(data=False, status_code=404, response=response) # Not Found + else: + return mk_resp(data=False, status_code=400, response=response) # Bad Request + + return mk_resp(data=response_data, response=response) +# ### END ### API Activity Log ### get_account_obj_activity_log_list() ### + + +@router.delete('/activity_log/{obj_id}', response_model=Resp_Body_Base) +async def delete_activity_log_obj( + obj_id: str = Query(..., min_length=1, max_length=22), + x_account_id: str = Header(...), + response: Response = Response, + ): + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + obj_type = 'activity_log' + result = delete_obj_template( + obj_type=obj_type, + obj_id=obj_id, + ) + return result diff --git a/app/routers/api_crud.py b/app/routers/api_crud.py index ea3e8e7..c65c74c 100644 --- a/app/routers/api_crud.py +++ b/app/routers/api_crud.py @@ -12,6 +12,7 @@ from app.models.response_models import * from app.models.account_models import * from app.models.account_cfg_models import * +from app.models.activity_log_models import * from app.models.address_models import * from app.models.archive_models import * from app.models.archive_content_models import * @@ -65,7 +66,7 @@ obj_type_li = {} obj_type_li['account'] = {'table_name': 'account', 'base_name': Account_Base} obj_type_li['account_cfg'] = {'table_name': 'v_account_cfg_detail', 'base_name': Account_Cfg_Base} # NOTE check view name: *_detail? -#obj_type_li['activity_log'] = {'table_name': 'activity_log', 'base_name': Activity_Log_Base} +obj_type_li['activity_log'] = {'table_name': 'activity_log', 'base_name': Activity_Log_Base} obj_type_li['address'] = {'table_name': 'v_address', 'base_name': Address_Base} obj_type_li['archive'] = {'table_name': 'v_archive', 'base_name': Archive_Base} obj_type_li['archive_content'] = {'table_name': 'v_archive_content', 'base_name': Archive_Content_Base}