import os
import subprocess
import textwrap
from app.dtos import User
from app.config import DATABASE_REPO_PATH, DATABASE_PR_AUTHOR
from app.utils import alert_message_and_exit
DATABASE_REPO_BASENAME = "database"
DATABASE_DML_PATH = "neo4j/neo4j-orchard/build/changelog/dml/"
PATH_TO_FILE = os.path.join(DATABASE_REPO_PATH, DATABASE_DML_PATH)
def indent(text, amount, ch=' '):
return textwrap.indent(text, amount * ch)
def check_creds_for_database_pr() -> bool:
if not os.path.isdir(DATABASE_REPO_PATH):
raise Exception(f"Error {DATABASE_REPO_PATH} it is not directory path update .env variable DATABASE_REPO_PATH")
if not os.path.isdir(PATH_TO_FILE):
raise Exception(f"Error {PATH_TO_FILE} it is not directory path to neo4j DB dml. Check DATABASE_REPO_PATH environment or code")
remote_origin_url = subprocess.run(
["git", "config", "--get", "remote.origin.url"],
capture_output=True,
text=True,
cwd=DATABASE_REPO_PATH
)
if remote_origin_url.returncode == 0:
remote_origin_url = remote_origin_url.stdout.strip()
# Run basename command
result = subprocess.run(["basename", "-s", ".git", remote_origin_url], capture_output=True, text=True)
# Check if the basename command was successful
if result.returncode == 0:
repo_name = result.stdout.strip()
if repo_name == DATABASE_REPO_BASENAME:
return True
else:
raise Exception(f"Database repo should be {DATABASE_REPO_BASENAME} not {repo_name}")
else:
raise Exception(f"Error occurs while trying to get repo data - {result.stderr.strip()}")
if not DATABASE_PR_AUTHOR:
raise Exception(f"Missing database pr author for. Set env variables for DATABASE_PR_AUTHOR")
def generate_vendors_access(vendors_ids: list[int], indent_amount=2) -> str:
base = ""
merge = ""
for index, vendor_id in enumerate(vendors_ids, 1):
line = f"(v{index}:Vendor {{id: {vendor_id} }})"
line += ",\n" if index < len(vendors_ids) else "\n"
base += indent(line, indent_amount)
merge += indent(f"MERGE (profile)-[:HAS_ACCESS_TO]->(v{index})\n", indent_amount-1)
return base + merge.rstrip('\n')
def create_user_query(
email, full_name, vendors_ids: list[int], jira_ticket, brand='sme'
) -> str:
first_name, last_name = full_name.split()
vendors_access = generate_vendors_access(vendors_ids, 1)
vendors_access = vendors_access.split('\n')
vendors_access = textwrap.dedent(vendors_access[0]) + "\n" + indent("\n".join(vendors_access[1:]), 4)
query = f"""
MERGE (increment:IncrementId {{nodeName: 'Profile'}})
ON CREATE SET increment.id = 2
ON MATCH SET increment.id = increment.id + 1
CREATE (profile:Profile {{
brand: '{brand}',
profileId: (increment.id-1),
profileType: 'AudienceProfile',
profileName: '{full_name}',
roles: ['audience'],
fullCatalogAccess: false,
uuid: apoc.create.uuid(),
createdAt: datetime(),
lastModifiedAt: datetime(),
createdBy: '{jira_ticket}',
updatedBy: '{jira_ticket}'
}})
WITH profile
MATCH {vendors_access}
WITH profile
CREATE (identity:Identity{{
email: '{email}',
firstName: '{first_name}',
lastName: '{last_name}',
name: '{full_name}',
id:apoc.create.uuid(),
active: 'Y',
createdAt: datetime(),
defaultBrand: '{brand}',
localization: 'en',
numberFormat: 'us',
createdBy: '{jira_ticket}',
updatedBy: '{jira_ticket}',
userTypes: ['label']
}})
MERGE (identity)-[:HAS_PROFILE]->(profile)
"""
return query
def create_profile_for_vendors_query(identity_id, full_name, vendors_ids, jira_ticket, brand='sme'):
query = f"""
MERGE (increment:IncrementId {{nodeName: 'Profile'}})
ON CREATE SET increment.id = 2
ON MATCH SET increment.id = increment.id + 1
CREATE (profile:Profile {{
brand: '{brand}',
profileId: (increment.id-1),
profileType: 'AudienceProfile',
profileName: '{full_name}',
roles: ['audience'],
fullCatalogAccess: false,
uuid: apoc.create.uuid(),
createdAt: datetime(),
lastModifiedAt: datetime(),
createdBy: '{jira_ticket}',
updatedBy: '{jira_ticket}'
}})
WITH profile
MATCH (i:Identity {{id: '{identity_id}' }}),
{generate_vendors_access(vendors_ids, 5)}
WITH profile,i
MERGE (i)-[:HAS_PROFILE]->(profile)
"""
return query
def update_access_to_vendors(identity_id, vendors_ids) -> str:
query = f"""
MATCH (i:Identity {{id: '{identity_id}' }})-[:HAS_PROFILE]-(profile:Profile {{profileType: "AudienceProfile"}}),
{generate_vendors_access(vendors_ids, 5)}
"""
return query
def build_query(users_data: list[User], vendors_ids, pr_name) -> str:
jira_ticket = pr_name.split("_")[0]
jira_ticket_link = f"https://theorchard.atlassian.net/browse/{jira_ticket}"
requests = []
for user in users_data:
if not user.exists:
requests.append(
create_user_query(user.email, user.name, vendors_ids, jira_ticket_link, user.brand)
)
elif not user.audience_profile:
requests.append(
create_profile_for_vendors_query(user.identity, user.name, vendors_ids, jira_ticket_link, user.brand)
)
else:
vendors_to_add = set(vendors_ids).difference(set(user.vendors_ids))
if vendors_to_add:
requests.append(
update_access_to_vendors(user.identity, vendors_to_add)
)
if not requests:
raise alert_message_and_exit("All users are up to date and there nothing to create or update using database query.")
changesets = []
for index, request in enumerate(requests, 1):
changeset = f'''
'''
changesets.append(changeset.strip('\n'))
changesets = '\n'.join(changesets)
changelog = f"""
{changesets}
"""
return changelog
def prepare_database_pr(pr_name: str, vendors_ids: list[int], users_data: list[User]):
branch_name = pr_name.replace("_", "-")
commit_message = pr_name.replace("-", " ")
file_name = f"{pr_name}.xml"
#TODO: validate users name contain first and last name
migration = build_query(users_data, vendors_ids, pr_name)
subprocess.run(["git", "checkout", "master"], cwd=DATABASE_REPO_PATH)
subprocess.run(["git", "checkout", "-b", branch_name], cwd=DATABASE_REPO_PATH)
with open(os.path.join(PATH_TO_FILE, file_name), "w") as f:
f.write(migration)
subprocess.run(["git", "add", file_name], cwd=PATH_TO_FILE)
print("New branch with DB migration file was created and commited. You need to push the code to remote and create PR.")
subprocess.run(["git", "commit", "-m", commit_message], cwd=DATABASE_REPO_PATH)
# subprocess.run(["git", "push", "-u", "origin", branch_name])
print("Copy command below and run from the database repo.")
print(f"git push -u origin {branch_name}")
# Create new user with AudienceProfile and access to vendor(-s)
# Add AudienceProfile for user that exist and access to vendor(-s)
# Add access to vendor(-s) if user with AudienceProfile already exist