def test_error_and_data(): with pytest.raises(AttributeError): doc = { "data": { "$type": "article" }, "errors": { "status": 200 } } json_api_doc.serialize(**doc)
def test_serialize_object_list(): data = [{ "$type": "article", "id": "1", "title": "Article 1" }, { "$type": "article", "id": "2", "title": "Article 2" }] doc = json_api_doc.serialize(data) assert doc == { "data": [{ "type": "article", "id": "1", "attributes": { "title": "Article 1" } }, { "type": "article", "id": "2", "attributes": { "title": "Article 2" } }] }
def test_serialize_object_embedded(): data = { "$type": "article", "id": "1", "title": "Article 1", "author": { "$type": "people", "id": "9", "name": "Bob" } } doc = json_api_doc.serialize(data) assert doc == { "data": { "type": "article", "id": "1", "attributes": { "title": "Article 1" }, "relationships": { "author": { "data": {"type": "people", "id": "9"} } } }, "included": [{ "type": "people", "id": "9", "attributes": { "name": "Bob", } }] }
def test_serialize_object_embedded_json(): data = { "$type": "article", "id": "1", "title": "Article 1", "author": { "$type": "people", "id": "100" }, "inner": { "value": "embedded regular JSON" }, "innerArray": [ "embedded", "regular", "JSON", "array" ], "innerObjectArray": [ { "value": "something" }, { "value": "something_else" } ] } doc = json_api_doc.serialize(data) assert doc == { "data": { "type": "article", "id": "1", "attributes": { "title": "Article 1", "inner": { "value": "embedded regular JSON" }, "innerArray": [ "embedded", "regular", "JSON", "array" ], "innerObjectArray": [ { "value": "something" }, { "value": "something_else" } ] }, "relationships": { "author": { "data": { "type": "people", "id": "100" } } } }, "included": [{ "type": "people", "id": "100" }] }
def execute(plan_id, app_filter, username, password, env): billing_api = config.get_billing_endpoint(env) plan_res = network.get( *auth.as_admin(env, "{}/v1/plans/{}".format(billing_api, plan_id))) plan = json_api_doc.deserialize(plan_res.json()) if "errors" in plan: print(plan["errors"][0]["detail"]) exit(1) print("[{}] Adding plan {} ({} months)".format(env, plan["name"], plan["period"]["length"])) # get all applications for the provided owner(username, password) apps_endpoint = config.get_apps_endpoint(env) + "/v1/apps" apps_res = network.get( *auth.as_user(username, password, env, apps_endpoint, {"query": { "page[limit]": 99999999 }})) apps = json_api_doc.deserialize(apps_res.json()) # filter apps out by those that match the provided regex app_re = re.compile(app_filter) filtered = list(filter(lambda app: app_re.match(app["name"]), apps)) apps_name_id = os.linesep.join( map(lambda app: "{} ({})".format(app["name"], app["id"]), filtered)) print("Plan {} will be purchased for following apps: {}{}".format( plan_id, os.linesep, apps_name_id)) if not prompt.yes_no("Do you want to continue?"): exit(0) for app in filtered: purchase_plan_endpoint = "{}/v1/accounts/me/subscriptions/application:{}/actions/recurly/subscribe".format( billing_api, app["id"]) purchase_res = network.post(*auth.as_user( username, password, env, purchase_plan_endpoint, { "body": json_api_doc.serialize( data={ "$type": "shoutem.billing.recurly-subscribe-actions", "billingInfoToken": None, "plan": { "$type": "shoutem.billing.plans", "id": plan_id } }) })) purchase = json_api_doc.deserialize(purchase_res.json()) if "errors" in purchase: print(purchase["errors"][0]["detail"] or purchase["errors"][0]["code"]) else: print("Plan purchased for {}".format(app["id"]))
def test_serialize_object_without_attributes(): data = { "$type": "article", "id": "1" } doc = json_api_doc.serialize(data) assert doc == { "data": { "type": "article", "id": "1" } }
def test_serialize_meta(): meta = { "some": "random", "silly": "data" } doc = json_api_doc.serialize(meta=meta) assert doc == { "meta": { "some": "random", "silly": "data" } }
def test_serialize_object(): data = {"$type": "article", "id": "1", "title": "Article 1"} doc = json_api_doc.serialize(data) assert doc == { "data": { "type": "article", "id": "1", "attributes": { "title": "Article 1" } } }
def test_serialize_errors(): errors = { "some": "random", "silly": "data" } doc = json_api_doc.serialize(errors=errors) assert doc == { "errors": { "some": "random", "silly": "data" } }
def test_serialize_object_embedded_list(): data = { "$type": "article", "id": "1", "title": "Article 1", "comments": [{ "$type": "comment", "id": "100", "content": "First" }, { "$type": "comment", "id": "101", "content": "Second" }] } doc = json_api_doc.serialize(data) assert doc == { "data": { "type": "article", "id": "1", "attributes": { "title": "Article 1" }, "relationships": { "comments": { "data": [{ "type": "comment", "id": "100" }, { "type": "comment", "id": "101" }] } } }, "included": [{ "type": "comment", "id": "100", "attributes": { "content": "First", } }, { "type": "comment", "id": "101", "attributes": { "content": "Second", } }] }
def _load_user_token(username, password, env, realm): realm_part = "realms/externalReference:{}/".format( realm) if realm != 0 else "" token_endpoint = "{}/v1/{}tokens".format(config.get_auth_endpoint(env), realm_part) creds = base64.b64encode("{}:{}".format( username, password).encode("UTF-8")).decode("UTF-8") refresh_response = network.post( token_endpoint, { "headers": { "Authorization": "Basic {}".format(creds) }, "body": json_api_doc.serialize({"$type": "shoutem.auth.tokens"}) }) parsed_refresh = json_api_doc.deserialize(refresh_response.json()) errors.exit_if_errors(parsed_refresh) access_response = network.post( token_endpoint, { "headers": { "Authorization": "Bearer {}".format(parsed_refresh["token"]) }, "body": json_api_doc.serialize({ "$type": "shoutem.auth.tokens", "tokenType": "access-token", "subjectType": "user", "compressionType": "gzip" }) }) access_parsed = json_api_doc.deserialize(access_response.json()) errors.exit_if_errors(access_parsed) tokens[(username, env)] = access_parsed["token"]
def execute(username, password, name, count, env): print("[{}] Creating {} new app(s) for user {}".format( env, count, username)) if not prompt.yes_no("Do you want to continue?"): exit(0) # create new apps by cloning the base app endpoint = config.get_apps_endpoint(env) + "/v1/apps/base/actions/clone" for i in range(count): app_name = "{} {}".format(name, i + 1) if count > 1 else name body = json_api_doc.serialize({ "$type": "shoutem.core.application-clones", "name": app_name, "alias": "base" }) res = network.post( *auth.as_user(username, password, env, endpoint, {"body": body})) res_parsed = json_api_doc.deserialize(res.json()) print(res_parsed["id"])
def test_serialize_links(): links = { "some": "random", "silly": { "href": "random", "meta": { "silly": "data" } } } doc = json_api_doc.serialize(links=links) assert doc == { "links": { "some": "random", "silly": { "href": "random", "meta": { "silly": "data" } } } }
def test_invalid(): with pytest.raises(AttributeError): json_api_doc.serialize({"a": 1})
def test_serialize_empty_list(): doc = json_api_doc.serialize([]) assert doc == { "data": [] }
def execute(src_id, dest_id, app_filter, recreate_subscription, plan_override, env): users_endpoint = config.get_auth_endpoint( env) + "/v1/realms/alias:default/users/legacyUser:{}" # get source and destination users, mostly for the sake of ensuring user inputed the right data src_user_res = network.get( *auth.as_admin(env, users_endpoint.format(src_id))) src_user = json_api_doc.deserialize(src_user_res.json()) errors.exit_if_errors(src_user) dest_user_res = network.get( *auth.as_admin(env, users_endpoint.format(dest_id))) dest_user = json_api_doc.deserialize(dest_user_res.json()) errors.exit_if_errors(dest_user) print("[{}] Transfering apps from {} to {}".format(env, src_user["username"], dest_user["username"])) # get all applications for the source user apps_endpoint = config.get_legacy_endpoint(env) + "/v1/apps" apps_res = network.get(*auth.as_admin( env, apps_endpoint, {"query": { "filter[owner.id]": src_id, "page[limit]": 99999999 }})) apps = json_api_doc.deserialize(apps_res.json()) errors.exit_if_errors(apps) account_endpoint = config.get_billing_endpoint( env) + "/v1/accounts/user:{}" src_account_res = network.get( *auth.as_admin(env, account_endpoint.format(src_id))) src_account = json_api_doc.deserialize(src_account_res.json()) errors.exit_if_errors(src_account) dest_account_res = network.get( *auth.as_admin(env, account_endpoint.format(dest_id))) dest_account = json_api_doc.deserialize(dest_account_res.json()) errors.exit_if_errors(dest_account) # we don't support different account types because it's too complicated, man if src_account["accountType"] != dest_account["accountType"]: print("Source and destination account must have the same account type") exit(1) # some accounts have old application subnscription groups which could cause confusion if src_account["applicationPlanGroup"] != dest_account[ "applicationPlanGroup"]: print( "Source and destination account must have the same application plan group" ) exit(1) subscription_endpoint = config.get_billing_endpoint( env) + "/v1/accounts/user:{}/subscriptions" src_subscriptions_res = network.get( *auth.as_admin(env, subscription_endpoint.format(src_id))) src_subscriptions = json_api_doc.deserialize(src_subscriptions_res.json()) errors.exit_if_errors(src_subscriptions) # filter apps out by those that match the provided regex app_re = re.compile(app_filter) filtered = list(filter(lambda app: app_re.match(app["name"]), apps)) apps_name_id = os.linesep.join( map(lambda app: "{} ({})".format(app["name"], app["id"]), filtered)) print("Apps from user {} will be transfered to user {}: {}{}".format( src_user["username"], dest_user["username"], os.linesep, apps_name_id)) if not prompt.yes_no("Do you want to continue?"): exit(0) # we only consider application subscription, agency transfer is not supported, just their apps is_valid_subscription = lambda sub, app: (sub[ "productType"] == "application" and sub["productId"] == app["id"] and sub["status"] == "subscribed") subscribe_endpoint = config.get_billing_endpoint( env ) + "/v1/accounts/{}/subscriptions/application:{}/actions/recurly/subscribe" unsubscribe_endpoint = config.get_billing_endpoint( env) + "/v1/accounts/{}/subscriptions/{}/actions/recurly/cancel" update_app_endpoint = config.get_legacy_endpoint(env) + "/v1/apps/{}" # throttle requests if many apps have to be transfered throttle = True if len(filtered) > 10 else False for app in filtered: existing_subscriptions = list( filter(lambda sub: is_valid_subscription(sub, app), src_subscriptions)) # cancel existing subscription if it exists, we can recreate it later # hopefully the app owner thansfer step bellow won't fail if existing_subscriptions: subscription = existing_subscriptions[0] unsubscribe_res = network.post(*auth.as_admin( env, unsubscribe_endpoint.format( src_account["id"], subscription["id"]), { "body": json_api_doc.serialize( {"$type": "shoutem.billing.subscriptions"}) })) canceled_subscriptions = json_api_doc.deserialize( unsubscribe_res.json()) errors.exit_if_errors(canceled_subscriptions) print("[{}] Canceled subscription for app {} ({})".format( env, app["id"], app["name"])) # .NET fails to parse included data in POST body, so we create JSON:API doc manually here # NodeJS services do not have this issue update_app_res = network.patch(*auth.as_admin( env, update_app_endpoint.format(app["id"]), { "body": { 'data': { 'type': 'shoutem.core.application', 'relationships': { 'owner': { 'data': { 'id': dest_user["legacyId"], 'type': 'shoutem.core.users' } } } } } })) update_app = json_api_doc.deserialize(update_app_res.json()) errors.exit_if_errors(update_app) print("[{}] Changed owner for app {} ({}) to {}".format( env, app["id"], app["name"], dest_user["username"])) # create a new subscription if user specified so and the previous owner had a subscription # this will cause new invoice on the new account, this probably won't be an issue with agencies if recreate_subscription and existing_subscriptions: subscription = existing_subscriptions[0] purchase_plan_id = subscription["plan"][ "id"] if plan_override == "" else plan_override purchase_res = network.post(*auth.as_admin( env, subscribe_endpoint.format(dest_account["id"], app["id"]), { "body": json_api_doc.serialize({ "$type": "shoutem.billing.recurly-subscribe-actions", "billingInfoToken": None, "plan": { "$type": "shoutem.billing.plans", "id": purchase_plan_id } }) })) purchase = json_api_doc.deserialize(purchase_res.json()) errors.exit_if_errors(purchase) print("[{}] Created subscription for app {} ({}) with plan {}". format(env, app["id"], app["name"], subscription["plan"]["id"])) if throttle: print("Throttling...") time.sleep(10)
def execute(moderator_email, moderator_password, app_filter, username, password, env): print("[{}] Adding moderator {}".format(env, moderator_email)) # get the agency for the provided owner agency_endpoint = config.get_apps_endpoint(env) + "/v1/agencies/mine" agency_res = network.get( *auth.as_user(username, password, env, agency_endpoint)) agency = json_api_doc.deserialize(agency_res.json()) errors.exit_if_errors(agency) # get existing moderators for agency moderators_endpoint = config.get_apps_endpoint(env) + "/v1/moderators" moderators_res = network.get(*auth.as_user( username, password, env, moderators_endpoint, {"query": { "filter[agency]": agency["id"], "page[limit]": 99999999 }})) moderators = json_api_doc.deserialize(moderators_res.json()) existing_moderator = list( filter(lambda mod: mod["user"]["username"] == moderator_email, moderators)) if existing_moderator: moderator = existing_moderator[0] # ask to create a new moderator for agency if one does not exist already elif prompt.yes_no("Moderator does not exist. Do you want to create it?"): create_moderator_endpoint = config.get_apps_endpoint( env) + "/v1/moderators/actions/create-for-user" create_moderator_res = network.post(*auth.as_user( username, password, env, create_moderator_endpoint, { "body": json_api_doc.serialize({ "$type": "shoutem.core.moderator-users", "password": moderator_password, "username": moderator_email, "agency": { "$type": "shoutem.core.agencies", "id": agency["id"] } }) })) try: moderator = json_api_doc.deserialize(create_moderator_res.json()) errors.exit_if_errors(moderator) except AttributeError: raise Exception("User with such email/password already exists") # exit if no moderators and user refused to create new one else: exit(0) # get all applications for the provided owner(username, password) apps_endpoint = config.get_apps_endpoint(env) + "/v1/apps" apps_res = network.get( *auth.as_user(username, password, env, apps_endpoint, {"query": { "page[limit]": 99999999 }})) apps = json_api_doc.deserialize(apps_res.json()) # filter apps out by those that match the provided regex app_re = re.compile(app_filter) filtered = list(filter(lambda app: app_re.match(app["name"]), apps)) apps_name_id = os.linesep.join( map(lambda app: "{} ({})".format(app["name"], app["id"]), filtered)) print("Moderator {} will be added to following apps: {}{}".format( moderator_email, os.linesep, apps_name_id)) if not prompt.yes_no("Do you want to continue?"): exit(0) for app in filtered: add_moderator_endpoint = "{}/{}/moderated-applications".format( moderators_endpoint, moderator["id"]) moderator_res = network.post(*auth.as_user( username, password, env, add_moderator_endpoint, { "body": json_api_doc.serialize({ "$type": "shoutem.core.moderator-applications", "role": "content-editor", "application": { "$type": "shoutem.core.applications", "id": app["id"] } }) })) try: json_api_doc.deserialize(moderator_res.json()) print("Moderator added for {}".format(app["id"])) except AttributeError: print("Moderator already exists for {}".format(app["id"]))
def execute(extensions_with_version, app_filter, user, env): apps_res = network.get(*auth.as_admin( env, "{}/v1/apps/".format(config.get_legacy_endpoint(env)), {"query": { "filter[owner.id]": user, }})) apps = json_api_doc.deserialize(apps_res.json()) errors.exit_if_errors(apps) # filter apps out by those that match the provided regex app_re = re.compile(app_filter) filtered = list(filter(lambda app: app_re.match(app["name"]), apps)) apps_name_id = os.linesep.join( map(lambda app: "{} ({})".format(app["name"], app["id"]), filtered)) print("[{}] Updating for following apps: {}{}".format( env, os.linesep, apps_name_id)) if not prompt.yes_no("Do you want to continue?"): exit(0) extension_re = re.compile("(.*)@(.*)") extension_name_ids = [] extensions_endpoint = config.get_extension_endpoint(env) for extension in extensions_with_version: match_res = extension_re.match(extension) if len(match_res.groups()) != 2: print("Invalid extension name@version: {}".format(extension)) exit(1) extension_name = match_res.group(1) extension_version = match_res.group(2) extension_res = network.get(*auth.as_admin( env, "{}/v1/extensions".format(extensions_endpoint), { "query": { "filter[canonicalName]": extension_name, "filter[version]": extension_version, } })) extensions = json_api_doc.deserialize(extension_res.json()) errors.exit_if_errors(extensions) if len(extensions) == 0: print("No extension {}".format(extension)) exit(1) extension_name_ids += [(extension_name, extensions[0]["id"])] apps_endpoint = config.get_apps_endpoint(env) for app in filtered: app_installations_res = network.get(*auth.as_admin( env, "{}/v1/apps/{}/installations".format(apps_endpoint, app["id"]))) app_installations = json_api_doc.deserialize( app_installations_res.json()) errors.exit_if_errors(app_installations) for extension_name, extension_id in extension_name_ids: current_installations = list( filter(lambda i: i["canonicalName"] == extension_name, app_installations)) if len(current_installations) == 0: print( "SKIPPING extension installation {} for app {}. {}".format( extension_name, app["id"], "Extension must be installed")) continue current_installation = current_installations[0] update_extension_endpoint = "{}/v1/apps/{}/installations/{}".format( apps_endpoint, app["id"], current_installation["id"]) update_res = network.patch(*auth.as_admin( env, update_extension_endpoint, { "body": json_api_doc.serialize( data={ "$type": "shoutem.core.installations", "id": current_installation["id"], "extension": extension_id }) })) update_res = json_api_doc.deserialize(update_res.json()) errors.exit_if_errors(update_res) print("Extension installation {} updated for app {}".format( extension_name, app["id"]))