validations and dockerfiles are updated

This commit is contained in:
2025-01-10 12:40:52 +03:00
parent f4f9e584ff
commit 4eb95e4d9c
107 changed files with 400185 additions and 1338 deletions

179
databases/__init__.py Normal file
View File

@@ -0,0 +1,179 @@
# SQL Models
from databases.sql_models.account.account import (
AccountBooks,
AccountCodeParser,
AccountRecords,
AccountCodes,
AccountDetail,
AccountMaster,
AccountRecordExchanges,
)
from databases.sql_models.building.budget import (
DecisionBookBudgetBooks,
DecisionBookBudgetCodes,
DecisionBookBudgetMaster,
DecisionBookBudgets,
)
from databases.sql_models.account.iban import (
BuildIbans,
BuildIbanDescription,
)
from databases.sql_models.api.encrypter import CrypterEngine
from databases.sql_models.building.build import (
Build,
BuildTypes,
BuildParts,
BuildArea,
BuildSites,
BuildLivingSpace,
BuildPersonProviding,
BuildCompaniesProviding,
RelationshipEmployee2Build,
)
from databases.sql_models.building.decision_book import (
BuildDecisionBook,
BuildDecisionBookItems,
BuildDecisionBookPerson,
BuildDecisionBookLegal,
BuildDecisionBookItemsUnapproved,
BuildDecisionBookInvitations,
BuildDecisionBookPayments,
BuildDecisionBookProjects,
BuildDecisionBookProjectPerson,
BuildDecisionBookPersonOccupants,
BuildDecisionBookProjectItems,
)
from databases.sql_models.company.company import (
Companies,
RelationshipDutyCompany,
)
from databases.sql_models.company.employee import (
Employees,
EmployeesSalaries,
EmployeeHistory,
Staff,
)
from databases.sql_models.company.department import (
Duty,
Duties,
Departments,
)
from databases.sql_models.event.event import (
Modules,
Services,
Service2Events,
Events,
Event2Occupant,
Event2Employee,
Event2OccupantExtra,
Event2EmployeeExtra,
)
from databases.sql_models.identity.identity import (
Addresses,
AddressCity,
AddressStreet,
AddressLocality,
AddressDistrict,
AddressNeighborhood,
AddressState,
AddressCountry,
AddressPostcode,
AddressGeographicLocations,
UsersTokens,
OccupantTypes,
People,
Users,
RelationshipDutyPeople,
RelationshipEmployee2PostCode,
Contracts,
)
from databases.sql_models.others.enums import (
ApiEnumDropdown,
)
from databases.sql_models.rules.rules import (
EndpointRestriction,
)
# NO-SQL Models
from databases.no_sql_models.mongo_database import (
MongoQuery,
)
from databases.no_sql_models.identity import (
MongoQueryIdentity,
)
__all__ = [
"AccountBooks",
"AccountCodeParser",
"AccountRecords",
"AccountCodes",
"AccountDetail",
"AccountMaster",
"AccountRecordExchanges",
"BuildIbans",
"BuildIbanDescription",
"CrypterEngine",
"Build",
"BuildTypes",
"BuildParts",
"BuildArea",
"BuildSites",
"BuildLivingSpace",
"BuildPersonProviding",
"BuildCompaniesProviding",
"BuildDecisionBook",
"BuildDecisionBookItems",
"BuildDecisionBookPerson",
"BuildDecisionBookLegal",
"BuildDecisionBookItemsUnapproved",
"BuildDecisionBookInvitations",
"BuildDecisionBookPayments",
"BuildDecisionBookProjects",
"BuildDecisionBookProjectPerson",
"BuildDecisionBookPersonOccupants",
"BuildDecisionBookProjectItems",
"DecisionBookBudgetBooks",
"DecisionBookBudgetCodes",
"DecisionBookBudgetMaster",
"DecisionBookBudgets",
"Companies",
"RelationshipDutyCompany",
"Employees",
"EmployeesSalaries",
"EmployeeHistory",
"Staff",
"Duty",
"Duties",
"Departments",
"Modules",
"Services",
"Service2Events",
"Events",
"Event2Occupant",
"Event2Employee",
"Event2OccupantExtra",
"Event2EmployeeExtra",
"Addresses",
"AddressCity",
"AddressStreet",
"AddressLocality",
"AddressDistrict",
"AddressNeighborhood",
"AddressState",
"AddressCountry",
"AddressPostcode",
"AddressGeographicLocations",
"UsersTokens",
"OccupantTypes",
"People",
"Users",
"RelationshipDutyPeople",
"RelationshipEmployee2PostCode",
"Contracts",
"ApiEnumDropdown",
"EndpointRestriction",
"RelationshipEmployee2Build",
# ------------------------------------------------
"MongoQuery",
"MongoQueryIdentity",
]

View File

@@ -0,0 +1,11 @@
from .selector_classes import (
Explanation,
SelectActionWithEmployee,
SelectAction,
)
__all__ = [
"Explanation",
"SelectAction",
"SelectActionWithEmployee",
]

View File

@@ -0,0 +1,361 @@
import uuid
import secrets
import hashlib
import requests
from sqlalchemy import or_
from datetime import timedelta
from fastapi.exceptions import HTTPException
from fastapi import status
from databases.no_sql_models.validations import (
PasswordHistoryViaUser,
AccessHistoryViaUser,
)
from api_library.date_time_actions.date_functions import system_arrow, client_arrow
from api_configs import ApiStatic, Auth
class PasswordModule:
@classmethod
def generate_token(cls, length):
return secrets.token_urlsafe(length)
@classmethod
def create_hashed_password(cls, domain, id_, password):
salted_password = f"{domain}-{id_}-{password}"
return hashlib.sha256(salted_password.encode()).hexdigest()
@classmethod
def check_hashed_password(cls, domain, id_, password, password_hashed):
if not password_hashed:
raise HTTPException(
status_code=401,
detail="Password is not changed yet user has no password.",
)
return cls.create_hashed_password(domain, id_, password) == password_hashed
class AuthModule(PasswordModule):
@classmethod
def check_user_exits(cls, access_key, domain):
from databases import Users
found_user = Users.query.filter(
or_(
Users.email == str(access_key).lower(),
Users.phone_number == str(access_key).replace(" ", ""),
),
).first()
if not found_user:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Given access key or domain is not matching with the any user record.",
)
other_domains_list = found_user.get_main_domain_and_other_domains(
get_main_domain=False
)
if domain not in other_domains_list:
raise HTTPException(
status_code=401,
detail=dict(message="Unauthorized User attempts to connect api"),
)
return found_user
def generate_access_token(self):
return self.generate_token(Auth.ACCESS_TOKEN_LENGTH)
def remove_refresher_token(self, domain, disconnect: bool = False):
from databases import (
UsersTokens,
)
if disconnect:
registered_tokens = UsersTokens.filter_all(
UsersTokens.user_id == self.id, system=True
)
else:
registered_tokens = UsersTokens.filter_all(
UsersTokens.domain == domain,
UsersTokens.user_id == self.id,
system=True,
)
registered_tokens.query.delete()
UsersTokens.save()
def check_password(self, password):
main_domain = self.get_main_domain_and_other_domains(get_main_domain=True)
if check_password := self.check_hashed_password(
domain=main_domain,
id_=str(self.uu_id),
password_hashed=self.hash_password,
password=password,
):
return check_password
raise HTTPException(
status_code=401,
detail="Password is not correct.",
)
def check_password_is_different(self, password):
main_domain = self.get_main_domain_and_other_domains(get_main_domain=True)
if self.hash_password == self.create_hashed_password(
domain=main_domain, id_=self.uu_id, password=password
):
raise HTTPException(
status_code=401,
detail="New password is same with old password.",
)
@staticmethod
def create_password(found_user, password, password_token=None):
from databases import MongoQueryIdentity
if found_user.password_token:
replace_day = 0
try:
replace_day = int(
str(found_user.password_expires_day or 0)
.split(",")[0]
.replace(" days", "")
)
except Exception as e:
err = e
token_is_expired = system_arrow.now() >= system_arrow.get(
str(found_user.password_expiry_begins)
).shift(days=replace_day)
if not password_token == found_user.password_token and token_is_expired:
raise HTTPException(
status_code=401,
detail="Password token is not valid. Please request a new password token.",
)
query_engine = MongoQueryIdentity(company_uuid=found_user.related_company)
domain_via_user = query_engine.get_domain_via_user(
user_uu_id=str(found_user.uu_id)
)["main_domain"]
new_password_dict = {
"password": found_user.create_hashed_password(
domain=domain_via_user, id_=str(found_user.uu_id), password=password
),
"date": str(system_arrow.now().date()),
}
history_dict = PasswordHistoryViaUser(
user_uu_id=str(found_user.uu_id),
password_add=new_password_dict,
access_history_detail={
"request": "",
"ip": "",
},
)
found_user.password_expiry_begins = str(system_arrow.now())
found_user.hash_password = new_password_dict.get("password")
found_user.password_token = "" if found_user.password_token else ""
query_engine.refresh_password_history_via_user(payload=history_dict)
found_user.save()
return found_user
@staticmethod
def reset_password_token(found_user):
found_user.password_expiry_begins = str(system_arrow.now())
found_user.password_token = found_user.generate_token(
Auth.REFRESHER_TOKEN_LENGTH
)
found_user.save()
return found_user.password_token
def generate_refresher_token(self, domain: str, remember_me=False):
from databases import (
UsersTokens,
)
if remember_me:
refresh_token = self.generate_token(Auth.REFRESHER_TOKEN_LENGTH)
if already_token := UsersTokens.filter_by_one(
system=True, user_id=self.id, token_type="RememberMe", domain=domain
).data:
already_token.update(token=refresh_token)
already_token.expires_at = system_arrow.shift(days=3)
already_token.save()
return refresh_token
users_tokens = UsersTokens.filter_by_all(
user_id=self.id, token_type="RememberMe", domain=domain, system=True
).data
if users_tokens:
users_tokens.query.delete()
UsersTokens.save()
users_token = UsersTokens.find_or_create(
user_id=self.id,
token_type="RememberMe",
token=refresh_token,
domain=domain,
)
users_token.save_and_confirm()
return refresh_token
return None
def remainder_day(self):
join_list = [
_ for _ in str(self.password_expires_day).split(",")[0] if _.isdigit()
]
return float(timedelta(days=int("".join(join_list))).seconds)
class UserLoginModule(AuthModule):
@classmethod
def login_user_with_credentials(cls, data, request):
from api_services.redis.auth_actions.auth import save_access_token_to_redis
from databases import (
Users,
People,
MongoQueryIdentity,
)
found_user = Users.check_user_exits(
access_key=data.access_key, domain=data.domain
)
access_token = found_user.generate_access_token()
query_engine = MongoQueryIdentity(company_uuid=found_user.related_company)
if found_user.check_password(password=data.password):
access_object_to_redis = save_access_token_to_redis(
request=request,
found_user=found_user,
domain=data.domain,
access_token=access_token,
)
refresher_token = found_user.generate_refresher_token(
domain=data.domain, remember_me=data.remember_me
)
headers_request = request.headers
headers_request = dict(headers_request)
headers_request["evyos-user-agent"] = headers_request.get("user-agent")
headers_request["evyos-platform"] = headers_request.get("user-agent")
headers_request["evyos-ip-ext"] = "94.54.68.158"
found_user.last_agent = headers_request.get("evyos-user-agent", None)
found_user.last_platform = headers_request.get("evyos-platform", None)
found_user.last_remote_addr = headers_request.get("evyos-ip-ext", None)
found_user.last_seen = str(system_arrow.now())
if ext_ip := headers_request.get("evyos-ip-ext"):
agent = headers_request.get("evyos-user-agent", "")
platform = headers_request.get("evyos-platform", "")
address = requests.get(f"http://ip-api.com/json/{ext_ip}").json()
address_package = {
"city": address["city"],
"zip": address["zip"],
"country": address["country"],
"countryCode": address["countryCode"],
"region": address["region"],
"regionName": address["regionName"],
}
mongo_db = MongoQueryIdentity(
company_uuid=str(found_user.related_company).replace(" ", ""),
storage_reasoning="AccessHistory",
)
filter_query = {
"agent": agent,
"platform": platform,
"address": address_package,
"user_id": found_user.id,
}
already_exits = mongo_db.mongo_engine.filter_by(filter_query) or None
no_address_validates = mongo_db.mongo_engine.get_all()[0] == 0
record_id = uuid.uuid4().__str__()
notice_link = ApiStatic.blacklist_login(record_id=record_id)
found_people = People.filter_one(People.id == found_user.person_id).data
access_via_user = query_engine.update_access_history_via_user(
AccessHistoryViaUser(
**{
"user_uu_id": found_user.uu_id.__str__(),
"access_history": {
"record_id": record_id,
"agent": agent,
"platform": platform,
"address": address_package,
"ip": ext_ip,
"access_token": access_token,
"created_at": system_arrow.now().timestamp(),
# "is_confirmed": True if no_address_validates else False,
# "is_first": True if no_address_validates else False,
},
}
)
)
if already_exits:
update_mongo = mongo_db.mongo_engine.table.update_one(
filter=filter_query,
update={
"$set": {
"ip": ext_ip,
"access_token": access_token,
"created_at": system_arrow.now().timestamp(),
}
},
)
else:
mongo_db.mongo_engine.insert(
payload={
"user_id": found_user.id,
"record_id": record_id,
"agent": agent,
"platform": platform,
"address": address_package,
"ip": ext_ip,
"access_token": access_token,
"created_at": system_arrow.now().timestamp(),
"is_confirmed": True if no_address_validates else False,
"is_first": True if no_address_validates else False,
}
)
found_user.remember_me = bool(data.remember_me)
found_user.save()
return {
"access_token": access_token,
"refresher_token": refresher_token,
"user": found_user,
"access_object": access_object_to_redis,
}
raise HTTPException(
status_code=401,
detail="Login is not successful. Please check your credentials.",
)
# UserLogger.log_error(
# dict(
# user_id=found_user.id,
# domain=data.domain,
# access_key=data.access_key,
# agent=found_user.last_agent,
# ip=getattr(request, "remote_addr", None)
# or request.headers.get("X-Forwarded-For", None),
# platform=found_user.last_platform,
# login_date=str(DateTimeLocal.now()),
# is_login=True,
# )
# )
# if (
# not str(found_people.country_code).lower()
# == str(address_package.get("countryCode")).lower()
# ):
# send_email_completed = send_email(
# subject=f"Dear {found_user.nick_name}, your password has been changed.",
# receivers=[str(found_user.email)],
# html=invalid_ip_or_address_found(
# user_name=found_user.nick_name,
# address=address_package,
# notice_link=notice_link,
# ),
# )
# if not send_email_completed:
# raise HTTPException(
# status_code=400,
# detail="An error occured at sending email. Please contact with support team.",
# )

View File

@@ -0,0 +1,79 @@
class Explanation: ...
class SelectorsBase:
@classmethod
def add_confirmed_filter(cls, first_table, second_table) -> tuple:
return (
first_table.active == True,
first_table.is_confirmed == True,
first_table.deleted == False,
second_table.active == True,
second_table.is_confirmed == True,
second_table.deleted == False,
)
class SelectActionWithEmployee:
@classmethod
def select_action(cls, employee_id, filter_expr: list = None):
if filter_expr is not None:
filter_expr = (cls.__many__table__.employee_id == employee_id, *filter_expr)
data = (
cls.session.query(cls.id)
.select_from(cls)
.join(cls.__many__table__, cls.__many__table__.member_id == cls.id)
.filter(
*filter_expr,
*SelectorsBase.add_confirmed_filter(
first_table=cls, second_table=cls.__many__table__
),
)
)
return cls.query.filter(cls.id.in_([comp[0] for comp in data.all()]))
data = (
cls.session.query(cls.id)
.select_from(cls)
.join(cls.__many__table__, cls.__many__table__.member_id == cls.id)
.filter(
cls.__many__table__.employee_id == employee_id,
*SelectorsBase.add_confirmed_filter(
first_table=cls, second_table=cls.__many__table__
),
)
)
return cls.query.filter(cls.id.in_([comp[0] for comp in data.all()]))
class SelectAction:
@classmethod
def select_action(cls, duty_id_list: list, filter_expr: list = None):
if filter_expr is not None:
data = (
cls.session.query(cls.id)
.select_from(cls)
.join(cls.__many__table__, cls.__many__table__.member_id == cls.id)
.filter(
cls.__many__table__.duties_id.in_(duty_id_list),
*SelectorsBase.add_confirmed_filter(
first_table=cls, second_table=cls.__many__table__
),
*filter_expr,
)
)
return cls.query.filter(cls.id.in_([comp[0] for comp in data.all()]))
data = (
cls.session.query(cls.id)
.select_from(cls)
.join(cls.__many__table__, cls.__many__table__.member_id == cls.id)
.filter(
cls.__many__table__.duties_id.in_(duty_id_list),
*SelectorsBase.add_confirmed_filter(
first_table=cls, second_table=cls.__many__table__
),
)
)
return cls.query.filter(cls.id.in_([comp[0] for comp in data.all()]))

View File

@@ -6,6 +6,20 @@ from databases.no_sql_models.validations import (
AccessHistoryViaUser,
)
from databases.no_sql_models.mongo_database import MongoQuery
from api_library.date_time_actions.date_functions import system_arrow
def validate_timestamp(doc):
"""Validate and fix timestamp fields in MongoDB documents"""
if not doc:
return doc
timestamp_fields = ['modified_at', 'created_at', 'accessed_at', 'timestamp']
for field in timestamp_fields:
if field in doc and not isinstance(doc[field], (int, float)):
# Convert to proper timestamp if it's not already
doc[field] = system_arrow.to_timestamp(doc[field])
return doc
class MongoQueryIdentity:
@@ -42,7 +56,7 @@ class MongoQueryIdentity:
"user_uu_id": payload.user_uu_id,
"other_domains_list": [payload.main_domain],
"main_domain": payload.main_domain,
"modified_at": datetime.datetime.now().timestamp(),
"modified_at": system_arrow.to_timestamp(system_arrow.now()),
},
)
@@ -52,14 +66,15 @@ class MongoQueryIdentity:
match=payload.user_uu_id,
payload={
"other_domains_list": payload.other_domains_list,
"modified_at": datetime.datetime.now().timestamp(),
"modified_at": system_arrow.to_timestamp(system_arrow.now()),
},
field="user_uu_id",
)
def get_domain_via_user(self, user_uu_id):
self.use_collection("Domain")
return self.mongo_engine.get_one(match=str(user_uu_id), field="user_uu_id")
result = self.mongo_engine.find(match=user_uu_id, field="user_uu_id")
return [validate_timestamp(doc) for doc in result] if result else None
def refresh_password_history_via_user(self, payload: PasswordHistoryViaUser):
self.use_collection("PasswordHistory")
@@ -96,14 +111,15 @@ class MongoQueryIdentity:
payload={
"password_history": password_history_list,
"access_history_detail": payload.access_history_detail,
"modified_at": datetime.datetime.now().timestamp(),
"modified_at": system_arrow.to_timestamp(system_arrow.now()),
},
field="user_uu_id",
)
def get_password_history_via_user(self, user_uu_id):
self.use_collection("PasswordHistory")
return self.mongo_engine.get_one(match=user_uu_id, field="user_uu_id")
self.use_collection("UserPasswordHistory")
result = self.mongo_engine.find(match=user_uu_id, field="user_uu_id")
return [validate_timestamp(doc) for doc in result] if result else None
def update_access_history_via_user(self, payload: AccessHistoryViaUser):
self.use_collection("AccessHistory")
@@ -119,7 +135,7 @@ class MongoQueryIdentity:
payload={
"user_uu_id": payload.user_uu_id,
"access_history": access_history,
"modified_at": datetime.datetime.now().timestamp(),
"modified_at": system_arrow.to_timestamp(system_arrow.now()),
},
field="user_uu_id",
)
@@ -127,14 +143,11 @@ class MongoQueryIdentity:
payload={
"user_uu_id": payload.user_uu_id,
"access_history": [payload.access_history],
"modified_at": datetime.datetime.now().timestamp(),
"modified_at": system_arrow.to_timestamp(system_arrow.now()),
}
)
def get_access_history_via_user(self, user_uu_id):
self.use_collection("AccessHistory")
return self.mongo_engine.filter_by(
payload={"user_uu_id": user_uu_id},
sort_by="modified_at",
sort_direction="desc",
)
result = self.mongo_engine.find(match=user_uu_id, field="user_uu_id")
return [validate_timestamp(doc) for doc in result] if result else None

View File

@@ -73,6 +73,7 @@ class CrudMixin(Base, SmartQueryMixin, SessionMixin, FilterAttributes):
]
creds = None # The credentials to use in the model.
lang = "tr" # The language to use in the model.
client_arrow: DateTimeLocal = None # The arrow to use in the model.
valid_record_dict: dict = {"active": True, "deleted": False}
valid_record_args = lambda class_: [class_.active == True, class_.deleted == False]
@@ -89,6 +90,7 @@ class CrudMixin(Base, SmartQueryMixin, SessionMixin, FilterAttributes):
def set_user_define_properties(cls, token):
cls.creds = token.credentials
cls.client_arrow = DateTimeLocal(is_client=True, timezone=token.timezone)
cls.lang = str(token.lang).lower()
@classmethod
def remove_non_related_inputs(cls, kwargs):
@@ -179,7 +181,7 @@ class CrudMixin(Base, SmartQueryMixin, SessionMixin, FilterAttributes):
"message": "",
}
return already_record
elif already_record.is_confirmed:
elif not already_record.is_confirmed:
already_record.meta_data = {
"created": False,
"error_case": "IsNotConfirmed",
@@ -202,7 +204,7 @@ class CrudMixin(Base, SmartQueryMixin, SessionMixin, FilterAttributes):
cls.created_by_id = cls.creds.get("person_id", None)
cls.created_by = cls.creds.get("person_name", None)
created_record.flush()
already_record.meta_data = {"created": True, "error_case": None, "message": ""}
created_record.meta_data = {"created": True, "error_case": None, "message": ""}
return created_record
@classmethod

View File

@@ -8,13 +8,14 @@ engine_config = {
"url": WagDatabase.DATABASE_URL,
"pool_size": 20,
"max_overflow": 10,
"echo": False,
"echo": True,
"echo_pool":True,
"isolation_level": "READ COMMITTED",
"pool_pre_ping": True,
}
engine = create_engine(**engine_config)
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False, echo=True)
engine = create_engine(**engine_config, )
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
session = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()

View File

@@ -45,6 +45,31 @@ class FilterAttributes:
self.__session__.delete(self)
self.__session__.commit()
@classmethod
def save_via_metadata(cls):
"""Save the data via the metadata."""
try:
meta_data = getattr(cls, "meta_data", {})
meta_data_created = meta_data.get("created", False)
if meta_data_created:
print('meta_data_created commit', meta_data_created)
cls.__session__.commit()
print('meta_data_created rollback', meta_data_created)
cls.__session__.rollback()
# cls.raise_http_exception(
# status_code="HTTP_304_NOT_MODIFIED",
# error_case=meta_data.get("error_case", "Error on save and commit"),
# data={},
# message=meta_data.get("message", "Error on save and commit"),
# )
except SQLAlchemyError as e:
cls.raise_http_exception(
status_code="HTTP_304_NOT_MODIFIED",
error_case=e.__class__.__name__,
data={},
message=str(e.__context__).split("\n")[0],
)
@classmethod
def save(cls):
"""Saves the updated model to the current entity db."""
@@ -52,11 +77,15 @@ class FilterAttributes:
cls.__session__.commit()
except SQLAlchemyError as e:
cls.raise_http_exception(
status_code="HTTP_400_BAD_REQUEST",
status_code="HTTP_304_NOT_MODIFIED",
error_case=e.__class__.__name__,
data={},
message=str(e.__context__).split("\n")[0],
)
@classmethod
def rollback(cls):
"""Rollback the current session."""
cls.__session__.rollback()
def save_and_confirm(self):
"""Saves the updated model to the current entity db."""