diff --git a/app/db_sql.py b/app/db_sql.py index 1c94c69..570e360 100644 --- a/app/db_sql.py +++ b/app/db_sql.py @@ -9,6 +9,7 @@ from .log import * from sqlalchemy import create_engine, text from sqlalchemy.exc import IntegrityError, OperationalError + db_uri = settings.SQLALCHEMY_DATABASE_URI connection_string = db_uri @@ -282,7 +283,7 @@ def sql_select( as_dict=True, as_list=None ): - log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.debug(locals()) if table_name and not (record_id or record_id_random or field_name or field_value or sql or data): diff --git a/app/lib_general.py b/app/lib_general.py index f386543..681067d 100644 --- a/app/lib_general.py +++ b/app/lib_general.py @@ -1,5 +1,5 @@ from __future__ import annotations -import datetime, redis +import datetime, pytz, redis #from datetime import datetime, time, timedelta from fastapi import APIRouter, Depends, Header, HTTPException, status diff --git a/app/main.py b/app/main.py index 56337a7..15644ff 100644 --- a/app/main.py +++ b/app/main.py @@ -1,4 +1,4 @@ -import logging, random # , uvicorn +import logging, os, random # , uvicorn from enum import Enum #from datetime import datetime, time, timedelta @@ -18,7 +18,7 @@ from .lib_general import * from .log import * # Import the routers here first: -from .routers import api_crud, api, account, address, archive, archive_content, contact, event, event_exhibit, event_registration, event_session, membership, order, order_cart, organization, page, person, post, post_comment, product, site, site_domain, user, websockets # , items, journals +from .routers import api_crud, api, account, address, archive, archive_content, contact, event, event_exhibit, event_registration, event_session, flask_cfg, membership, order, order_cart, organization, page, person, post, post_comment, product, site, site_domain, user, websockets # , items, journals from .db_sql import db @@ -61,6 +61,15 @@ app.include_router( #dependencies=[Depends(get_account_header)], #responses={404: {'description': 'Not found'}}, ) +app.include_router( + flask_cfg.router, + prefix='/flask_cfg', + tags=['Flask CFG'], + #dependencies=[Depends(get_token_header)], + #dependencies=[Depends(get_account_header)], + #responses={404: {'description': 'Not found'}}, +) + app.include_router( account.router, prefix='/account', diff --git a/app/models/account_cfg_model.py b/app/models/account_cfg_model.py index 5dc3f01..0fdf758 100644 --- a/app/models/account_cfg_model.py +++ b/app/models/account_cfg_model.py @@ -24,6 +24,14 @@ class Account_Cfg_Base(BaseModel): account_id_random: Optional[str] account_id: Optional[int] + account_code: Optional[str] + account_name: Optional[str] + account_description: Optional[str] + + account_enable: Optional[bool] + account_enable_from: Optional[datetime.datetime] = None + account_enable_to: Optional[datetime.datetime] = None + show_user_availability: Optional[bool] show_person_create: Optional[bool] person_create_label: Optional[str] diff --git a/app/models/common_field_schema.py b/app/models/common_field_schema.py index 6eba62f..d1b02f5 100644 --- a/app/models/common_field_schema.py +++ b/app/models/common_field_schema.py @@ -39,6 +39,7 @@ base_fields['event_presenter_id_random'] = xxx_id_random_field_schema base_fields['event_registration_id_random'] = xxx_id_random_field_schema base_fields['event_session_id_random'] = xxx_id_random_field_schema base_fields['event_track_id_random'] = xxx_id_random_field_schema +base_fields['flask_cfg_id_random'] = xxx_id_random_field_schema base_fields['fundraising_id_random'] = xxx_id_random_field_schema base_fields['hosted_file_id_random'] = xxx_id_random_field_schema base_fields['membership_id_random'] = xxx_id_random_field_schema diff --git a/app/models/flask_cfg_model.py b/app/models/flask_cfg_model.py new file mode 100644 index 0000000..fb80a06 --- /dev/null +++ b/app/models/flask_cfg_model.py @@ -0,0 +1,57 @@ +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 ..lib_general import * + +from .common_field_schema import base_fields, default_num_bytes + + +class Flask_Cfg_Base(BaseModel): + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + id_random: Optional[str] = Field( + **base_fields['flask_cfg_id_random'], + alias='flask_cfg_id_random', + default_factory=lambda:secrets.token_urlsafe(default_num_bytes), + ) + id: Optional[int] = Field( + #alias='flask_cfg_id' + ) + + code: Optional[str] + name: Optional[str] + notes_description: 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('flask_cfg_id_random', always=True) + def flask_cfg_id_random_copy(cls, v, values, **kwargs): + log.setLevel(logging.WARNING) + log.debug(locals()) + + if values['id_random']: + return values['id_random'] + return None + + @validator('id', always=True) + def flask_cfg_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='flask_cfg') + return None + + class Config: + underscore_attrs_are_private = True + fields = base_fields + +#Flask_Cfg_Base.update_forward_refs() diff --git a/app/models/response_model.py b/app/models/response_model.py index 309c1c3..23768fa 100644 --- a/app/models/response_model.py +++ b/app/models/response_model.py @@ -22,7 +22,7 @@ class Resp_Body_Base(BaseModel): # The make response function for REST - STI 2021-03-05 -def mk_resp(data={}, dict_to_json=None, status_code=200, status_message=None, status_name=None, success=True, details=None, by_alias=True, exclude_unset=True): +def mk_resp(data:dict={}, dict_to_json:bool=None, status_code:int=200, status_message:str=None, status_name:str=None, success:bool=True, details:bool=None, by_alias:bool=True, exclude_unset:bool=True): log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.debug(locals()) diff --git a/app/routers/flask_cfg.py b/app/routers/flask_cfg.py new file mode 100644 index 0000000..ba95472 --- /dev/null +++ b/app/routers/flask_cfg.py @@ -0,0 +1,134 @@ +import datetime +#from datetime import datetime, time, timedelta +from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query, status +from pydantic import BaseModel, EmailStr, Field +from typing import Dict, List, Optional, Set, Union + +from ..lib_general import * +from app.config import settings +from app.db_sql import * + +from .api_crud import delete_obj_template, get_obj_template, get_obj_li_template, patch_obj_template, post_obj_template + +from ..models.response_model import * +from ..models.flask_cfg_model import Flask_Cfg_Base + + +router = APIRouter() + + +@router.post('', response_model=Resp_Body_Base) +async def post_flask_cfg_obj( + obj: Flask_Cfg_Base, + x_account_id: str = Header(...), + return_obj: Optional[bool] = True, + by_alias: Optional[bool] = True, + include: Optional[list] = [], + exclude: Optional[list] = [], + exclude_unset: Optional[bool] = True, + exclude_none: Optional[bool] = True, + ): + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + obj_type = 'flask_cfg' + obj_data_dict = obj.dict(by_alias=False, exclude_unset=True) + result = post_obj_template( + obj_type=obj_type, + data=obj_data_dict, + return_obj=True, + by_alias=True, + exclude_unset=True, + ) + return result + + +@router.patch('/{obj_id}', response_model=Resp_Body_Base) +async def patch_flask_cfg_obj( + obj_id: str = Query(..., min_length=1, max_length=22), + obj: Flask_Cfg_Base = None, + x_account_id: Optional[str] = Header(..., ), + return_obj: Optional[bool] = True, + by_alias: Optional[bool] = True, + include: Optional[list] = [], + exclude: Optional[list] = [], + exclude_unset: Optional[bool] = True, + exclude_none: Optional[bool] = True, + ): + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + obj_type = 'flask_cfg' + obj_data_dict = obj.dict(by_alias=False, exclude_unset=True) + obj_data_dict['id'] = redis_lookup_id_random(record_id_random=obj_id, table_name=obj_type) + obj_data_dict['id_random'] = obj_id + result = patch_obj_template( + obj_type=obj_type, + data=obj_data_dict, + obj_id=obj_id, + return_obj=True, + by_alias=True, + exclude_unset=True, + ) + return result + + +@router.get('/list', response_model=Resp_Body_Base) +async def get_flask_cfg_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, + include: Optional[list] = [], + exclude: Optional[list] = [], + exclude_unset: Optional[bool] = True, + exclude_none: Optional[bool] = True, + ): + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + obj_type = 'flask_cfg' + 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('/{flask_cfg_id}', response_model=Resp_Body_Base) +async def get_flask_cfg_obj( + flask_cfg_id: int = None, + #flask_cfg_id: str = Query(..., min_length=1, max_length=22), + x_account_id: str = Header(...), + by_alias: Optional[bool] = True, + include: Optional[list] = [], + exclude: Optional[list] = [], + exclude_unset: Optional[bool] = True, + exclude_none: Optional[bool] = True, + ): + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + if sql_select_result := sql_select(table_name='flask_cfg', record_id=flask_cfg_id): + return mk_resp(data=sql_select_result) + else: + return mk_resp(data=None, status_code=404) + + +@router.delete('/{obj_id}', response_model=Resp_Body_Base) +async def delete_flask_cfg_obj( + obj_id: str = Query(..., min_length=1, max_length=22), + x_account_id: str = Header(...), + ): + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + obj_type = 'flask_cfg' + result = delete_obj_template( + obj_type=obj_type, + obj_id=obj_id, + ) + return result \ No newline at end of file diff --git a/app/routers/user.py b/app/routers/user.py index 0b761e8..c2c0c4a 100644 --- a/app/routers/user.py +++ b/app/routers/user.py @@ -1,4 +1,4 @@ -import datetime +import datetime, pytz, time #from datetime import datetime, time, timedelta from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query, status from pydantic import BaseModel, EmailStr, Field @@ -68,6 +68,74 @@ async def patch_user_obj( return result +# This will look up a user based on the auth key given +# This can only be done once per key. It will be deleted if found +# A new one will need to be requested for a particular user each time +@router.get('/authenticate/auth_key/{auth_key}', response_model=Resp_Body_Base) +async def auth_key_get_user_obj( + auth_key: str = Query(..., min_length=11, max_length=22), + x_account_id: str = Header(...), + by_alias: Optional[bool] = True, + exclude_unset: Optional[bool] = True, + exclude_none: Optional[bool] = True, + ): + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + if sql_select_result := sql_select(table_name='user', field_name='auth_key', field_value=auth_key): + log.debug(sql_select_result) + + resp_data = {} + resp_data['user_id_random'] = sql_select_result.get('id_random') + resp_data['username'] = sql_select_result.get('username') + resp_data['enable'] = sql_select_result.get('enable') + resp_data['enable_from'] = sql_select_result.get('enable_from') + resp_data['enable_to'] = sql_select_result.get('enable_to') + try: + user_obj = User_Base(**sql_select_result).dict(by_alias=by_alias, exclude_unset=exclude_unset, exclude_none=exclude_none) + log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(user_obj) + log.debug(user_obj.get('enable_from', None)) + except ValidationError as e: + log.error(e.json()) + + current_utc_datetime = datetime.datetime.now(datetime.timezone.utc) + + log.debug(user_obj.get('enable_from', None).astimezone(pytz.UTC)) + user_enable_from = user_obj.get('enable_from', None).astimezone(pytz.UTC) + log.debug(user_enable_from) + + log.debug(user_obj.get('enable_to', None)) + user_enable_to = user_obj.get('enable_to', None).astimezone(pytz.UTC) + log.debug(user_enable_to) + + if resp_data['enable']: pass + else: + log.info('The user account has been disabled') + if user_enable_from <= current_utc_datetime: + log.info('Enable from datetime is valid') + else: + log.info('Enable from datetime is in the future. Please wait.') + if user_enable_to >= current_utc_datetime: + log.info('Enable to datetime is valid') + else: + log.info('Enable to datetime is in the past. Your user account has been disabled.') + + update_data = {} + update_data['id'] = sql_select_result.get('id') + update_data['auth_key'] = None + + if sql_update_resp := sql_update(table_name='user', data=update_data): + log.info('The user record was updated with a NULL auth_key') + else: + log.info('The user record was not updated with a NULL auth_key') + log.debug(sql_update_resp) + + return mk_resp(data=user_obj) + else: + return mk_resp(data=None, status_code=404) + + @router.get('/list', response_model=Resp_Body_Base) async def get_user_obj_li( for_obj_type: Optional[str] = Query(None, min_length=2, max_length=50),