# endDate = date(2020, 9, 1).isoformat() # startDate = date(2020, 9, 1).isoformat() # endDate = date(2020, 11, 20).isoformat() startDate = date(2020, 11, 20).isoformat() endDate = date(2020, 12, 5).isoformat() queryParams = f'?templateId=5f037d852d421&limit=100&fromDts={startDate}&toDts={endDate}' data = '' url = S_baseURL + resourcePath + queryParams ## Uncomment the following three lines to do a command line query for a specific name # firstName = input("First name: ") # lastName = input("Last name: ") # url = baseURL + resourcePath + queryParams + f'&firstName={firstName}&lastName={lastName}' # Make request response = apiCall(httpVerb, url, data, S_headers) print(f"Signed waivers retrieved: {len(response['waivers'])}") # Smartwaiver returns a max of 100 waivers and does not indicate when there is additional data to retrieve # Print warning to the console to alert users there may be missing data. if len(response['waivers']) == 100: print( "WARNING - Max responses received. Results may not be complete. Try resubmitting query with tighter date range." ) # Convert to pandas DataFrame for data cleaning and merging with Neon info jsonSigned = json.dumps(response['waivers']) signed = pd.read_json(jsonSigned) signed = signed.rename(columns={ "firstName": "First Name", "lastName": "Last Name"
neonFilename = "Neon/memberAccounts.json" neonAccounts = {} # ##### NEON ###### with open(neonFilename) as neonFile: neonAccountJson = json.load(neonFile) for account in neonAccountJson: neonAccounts[neonAccountJson[account] ["Account ID"]] = neonAccountJson[account] ### NOTE this GET has a limit of 1000 users. If we grow that big, this will be the least of our problems httpVerb = 'GET' resourcePath = f'/users?offset=0&sort=identity.lastName&order=asc' url = url = O_baseURL + resourcePath opResponse = apiCall(httpVerb, url, "", O_headers) #pprint(opResponse) opUsers = {} if opResponse.get("data"): for i in opResponse.get("data"): opUsers[i["id"]] = i opExists = 0 opSuspended = 0 opSubscribers = 0 opMissingWaiver = 0 opMissingTour = 0 opNotReady = 0 opReady = 0
import json from config import D_APIkey, D_APIuser from util import apiCall ### Discourse Account Info D_baseURL = 'https://yo.atxhs.org' D_headers = {'Api-Key': D_APIkey, 'Api-Username': D_APIuser} # Request Info # Get a list of all active members on Discourse httpVerb = 'GET' resourcePath = '/admin/users/list/active.json' data = '' url = D_baseURL + resourcePath usersResponse = apiCall(httpVerb, url, data, D_headers) # # Print to file # with open('./Discourse/users.json', 'w') as outfile: # json.dump(usersResponse, outfile, indent=4) print(usersResponse[0]["username"]) print(usersResponse[0]["name"]) # # Request Info # httpVerb ='GET' # # resourcePath = '/groups/haxor.json' # id 42 # # resourcePath = '/groups/members2B.json' # id 52 # data = '' # # Request Info
for account in neonAccountJson: neonAccountList.append(neonAccountJson.get(account)) #check if we have user data cached. If we do, use it. if os.path.exists(discourseFilename) and os.access(discourseFilename, os.R_OK): with open(discourseFilename) as discourseFile: fullDlist = json.load(discourseFile) else: #before doing all the Discourse-fetching, make sure we can write our output file outfile = open(discourseFilename, 'w') while True: resourcePath = f'/admin/users/list/active.json?page={page}' url = D_baseURL + resourcePath memberResponse = apiCall(httpVerb, url, data, D_headers) fullDlist = fullDlist + memberResponse if len(memberResponse) < 100: break else: print(f'{len(fullDlist)} active users retrieved from Discourse... Querying for more data.') page += 1 # Fetching emails and checking for weirdness (the weirdness is not coming from here.) # this email check is janky somehow - sometimes we get "email":null for accounts with valid emails in their Discourse profile # so far the error count has been low enough I've just been updating them in Neon manually for i, response in enumerate(fullDlist): dID = response['username'] resourcePath = f'/users/{dID}/emails.json' url = D_baseURL + resourcePath emailResponse = apiCall(httpVerb, url, data, D_headers)
########### ATXHS NeonCRM & Discourse API Integrations ############ # Neon API docs - https://developer.neoncrm.com/api-v2/ # ################################################################# from pprint import pprint import base64 from config import N_APIkey, N_APIuser from util import apiCall # Neon Account Info N_auth = f'{N_APIuser}:{N_APIkey}' N_baseURL = 'https://api.neoncrm.com/v2' N_signature = base64.b64encode(bytearray(N_auth.encode())).decode() N_headers = { 'Content-Type': 'application/json', 'Authorization': f'Basic {N_signature}', 'NEON-API-VERSION': '2.1' } ##### NEON ##### # Get possible search fields for POST to /accounts/search httpVerb = 'GET' resourcePath = '/accounts/search/searchFields' queryParams = '' data = '' url = N_baseURL + resourcePath + queryParams response = apiCall(httpVerb, url, data, N_headers) print("### SEARCH FIELDS ###\n") pprint(response)
if (neonAccounts[account].get("validMembership") and not neonAccounts[account].get("AccessSuspended") and not neonAccounts[account].get("FacilityTourDate")): print( f'Populating FacilityTourDate for {neonAccounts[account].get("First Name")} {neonAccounts[account].get("Last Name")} ({neonAccounts[account].get("Account ID")})' ) if not dryRun: ##### NEON ##### # Update part of an account # https://developer.neoncrm.com/api-v2/#/Accounts/patchAccount httpVerb = 'PATCH' resourcePath = f'/accounts/{neonAccounts[account].get("Account ID")}' queryParams = '?category=Account' data = f''' {{ "individualAccount": {{ "accountCustomFields": [ {{ "id": "182", "name": "FacilityTourDate", "value": "01/01/1970", "status": "ACTIVE" }} ] }} }} ''' url = N_baseURL + resourcePath + queryParams patch = apiCall(httpVerb, url, data, N_headers) pprint(patch)
"Preferred Name", "Account ID", "Membership Expiration Date", "Membership Start Date", 85 ], "pagination": {{ "currentPage": 0, "pageSize": 200 }} }} ''' url = N_baseURL + resourcePath + queryParams neon_accounts = {} responseAccounts = apiCall(httpVerb, url, data, N_headers) # exit() #re-shuffle the data into a format that's a little easier to work with for acct in responseAccounts["searchResults"]: neon_accounts[acct["Account ID"]] = acct #Because Neon API is dumb, the "expiration date" of an account might not be real -- #The API counts failed renewals as valid as long as automatic renewal is enabled httpVerb = 'GET' resourcePath = '/accounts/search' queryParams = '' data = '' for account in neon_accounts: