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