Skip to content

Commit 2e42d65

Browse files
Add new APIs to support new developer portal (#14)
This is the big appstore-api update to support the new developer portal -- it is the backend that matches the frontend @ https://github.com/pebble-dev/rebble-dev-portal/tree/devPortal2 . No changes to database schema or sqlalchemy models have been made -- just new web APIs. * Inital push of devportal2 apis. Currently added one developer-centric 'about me' api * Start work on new app API * For now, add copy functions for in-memory uploading to s3 rather than from on-disk * Add appstore submission validation function * Add developer portal API under /api/v2. With working submit of new app * For now, add copy file with in-memory pbw functions. This is temporary * Add aws session token to .gitignore. Just in case * Update app -> watchapp to match api * Update new app submission validator to match new api field names * Add update appstore listing fields api * app.notfound now returns 400. Fix updating descriptions * Handle dodgy appinfo.json. Need to add an appinfo.json validator * If users performs GET on update app endpoint, redirect to existing getappdetails API * Return developer name instead of user name on /me/developer. Add POST request option to /me/developer to update developer name * Validate appinfo.json. Handle no appinfo in pbw * Add appinfo.json validator * Remove unnecessary import * Add app visibility toggle to api. Todo: handle agolia * Add onboarding API to create new developer accounts. Handle request developer info for Rebble accounts with no developer account * Return the user's rebble auth name if they do not have a developer account yet * Move platform validation into standalone util * Add screenshot API (Get only atm) * Always return authName. We use this for account recovery * Add delete screenshot api * Don't allow the last screenshot for any given platform to be deleted * Check users owns app before deleting screenshots * Add screenshot upload api * Remove platform agnostic screenshots. We're no longer doing that * Return is_wizard with /me/developer * Add discord integration. Add new release API. Clean up some old code. Handle junk pbws on submit api * Forgot to add new discord file! * Clean up imports in discord.py * Announce new apps on creation, not edit * Add 2 wizard apis. Fix bug with announce to discord. Update agolia after app edit * Update agolia code on delete. Currently guessing here * Merge pbw and pbw_in_memory into one nice file. Let DB do deletion. Change wording of 1 corrupt pbw error message so we can tell which is which * Update pbw.py to handle file paths or file objects in one function. pbw_in_memory now redundant and deleted * Handle app submission with no developer account. Shouldn't ever be possible but still * Clearup debug logging * Clear up more testing stuff * Minor consistency fixes * Remove option for generic screenshots * Validate submission type is watchface or watchapp * Merge in_memory and from file functions in s3.py. Remove blanks and duplicates from wizard get s3 assets function * Use image url generator instead of hardcoded asset url * Add API for wizard app developer transfer * Update appstore/developer_portal_api.py Fix indentation Co-authored-by: Katharine Berry <[email protected]> * Inital post-code-review changes * Apply suggestions from code review (Part 2) All the small bits from KB's review. Co-authored-by: Katharine Berry <[email protected]> * Replace edit listing API's if statements with setattr() * More review fixes. Remove redirect. If not X in Y -> If X not in Y. Remove some Except statements * Correct cases in Discord function. Remove catch statement * Fix if statement typo * Change camelCase variables to snake_case * Replace generic except with specific error * Remove field whitelist from /submit. Remove blanking of optional parameters * Replace generic excepts with specific errors * Merge the two invalid pbw try statements * Deconstruct is_appinfo_valid response * Remove unnecessary try wrapper * Move discord webhook error handling to calling function * Ask for forgiveness with new screenshot error. Add discord log message * Camels are now snakes * EAFP & Remove 302 redirect to correct api for GETting screenshots * Snakes * EAFP * Cleaner screenshot removal * Be more specific about exception * snek * sssss * Be more specific about exception * Tidy up Discord integration * Make auth check and is_wizard checks into util functions * /api/v2 -> /api/dp * Check new version is greater than old version, not equal to * Break long return message into multiple lines * Replace generic Exception handler with BadRequest handler * Fail if we can't detect the mime type from the filename. No longer default to png * Raise KeyError if algolia setup isn't present. Add optional override to this behaviour for dev. Currently doesn't kill flask * Replace generic exception with FileNotFoundError * Only look for 'screenshot' on single new image api instead of taking the file file we find * Validate image dimensions. Do this on new submission api and new screenshot api * Remove spaces surrounding equals sign when it's a named parameter * Update comment on pbw function inputs * Update new app validation function to throw exceptions instead of returning a tuple * Add demand_authed_request * Add missing import * Add screenshot range start. Thereby ignoring screenshot-platform-0 * Add wizard discord audit log * No longer throw typeerror on invalid field on app field edit api Co-authored-by: Katharine Berry <[email protected]>
1 parent 12055ad commit 2e42d65

File tree

9 files changed

+1070
-38
lines changed

9 files changed

+1070
-38
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
.env
33
.idea/
44
__pycache__/
5+
session-token.json

appstore/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from .models import init_app as init_models
1818
from .api import init_app as init_api
1919
from .dev_portal_api import init_app as init_dev_portal_api
20+
from .developer_portal_api import init_app as init_developer_portal_api
2021
from .commands import init_app as init_commands
2122
from .utils import init_app as init_utils
2223
from .locker import locker
@@ -35,6 +36,7 @@
3536
init_utils(app)
3637
init_api(app)
3738
init_dev_portal_api(app)
39+
init_developer_portal_api(app)
3840
init_commands(app)
3941

4042
@app.route('/heartbeat')

appstore/dev_portal_api.py

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from algoliasearch import algoliasearch
22
from flask import Blueprint, jsonify, abort, request
33
from flask_cors import CORS
4+
from werkzeug.exceptions import BadRequest
45
from sqlalchemy.exc import IntegrityError
56
from sqlalchemy.orm.exc import NoResultFound
67

7-
from .utils import authed_request, get_uid
8-
from .models import LockerEntry, UserLike, db, App
8+
from .utils import authed_request, demand_authed_request, get_uid
9+
from .models import LockerEntry, UserLike, db, App, Developer
910
from .settings import config
1011

1112
parent_app = None
@@ -21,9 +22,7 @@
2122

2223
@legacy_api.route('/users/me')
2324
def me():
24-
result = authed_request('GET', f"{config['REBBLE_AUTH_URL']}/api/v1/me/pebble/appstore")
25-
if result.status_code != 200:
26-
abort(401)
25+
result = demand_authed_request('GET', f"{config['REBBLE_AUTH_URL']}/api/v1/me/pebble/appstore")
2726
me = result.json()
2827
rebble_id = me['rebble_id']
2928
added_ids = [x.app_id for x in LockerEntry.query.filter_by(user_id=rebble_id)]
@@ -42,6 +41,79 @@ def me():
4241
})
4342

4443

44+
@legacy_api.route('/users/me/developer', methods=['GET'])
45+
def my_apps():
46+
result = demand_authed_request('GET', f"{config['REBBLE_AUTH_URL']}/api/v1/me/pebble/appstore")
47+
me = result.json()
48+
49+
developer_id = me['id']
50+
# Get apps
51+
my_apps = [x for x in App.query.filter_by(developer_id=developer_id)]
52+
my_appdata = [{"id": a.id, "title": a.title} for a in my_apps]
53+
54+
# Get developer name
55+
developer = Developer.query.filter_by(id=developer_id).one_or_none()
56+
57+
# Check if is wizard (Can we update auth to return this with /me/pebble/appstore?)
58+
result = demand_authed_request('GET', f"{config['REBBLE_AUTH_URL']}/api/v1/me")
59+
me_detailed = result.json()
60+
me["is_wizard"] = me_detailed["is_wizard"]
61+
62+
if developer is None:
63+
return jsonify({
64+
'id': me["id"],
65+
'userid': me["uid"],
66+
'href': request.url,
67+
'authName': me["name"],
68+
'applications': [],
69+
'needsSetup': True,
70+
'w': me["is_wizard"],
71+
})
72+
else:
73+
return jsonify({
74+
'id': me["id"],
75+
'userid': me["uid"],
76+
'href': request.url,
77+
'authName': me["name"],
78+
'applications': my_appdata,
79+
'name': developer.name,
80+
'needsSetup': False,
81+
'w': me["is_wizard"],
82+
})
83+
84+
@legacy_api.route('/users/me/developer', methods=['POST'])
85+
def update_my_developer():
86+
permitted_fields = ["name"]
87+
88+
try:
89+
req = request.json
90+
except BadRequest as e:
91+
print(e)
92+
return jsonify(error="Invalid POST body. Expected JSON", e="body.invalid"), 400
93+
94+
if req is None:
95+
return jsonify(error="Invalid POST body. Expected JSON and 'Content-Type: application/json'", e="request.invalid"), 400
96+
97+
for f in req:
98+
if not f in permitted_fields:
99+
return jsonify(error=f"Illegal field: {f}", e="illegal.field"), 400
100+
101+
if not "name" in req:
102+
return jsonify(error=f"Missing required field: name", e="missing.field.name"), 400
103+
104+
# Resolve our auth token to our developer ID
105+
result = demand_authed_request('GET', f"{config['REBBLE_AUTH_URL']}/api/v1/me/pebble/appstore")
106+
me = result.json()
107+
developer_id = me["id"]
108+
109+
developer = Developer.query.filter_by(id=developer_id).one()
110+
print("Update developer from " + developer.name + " to " + req["name"])
111+
developer.name = req["name"]
112+
db.session.commit()
113+
114+
return jsonify(success=True, id=developer.id, name=developer.name)
115+
116+
45117
@legacy_api.route('/applications/<app_id>/add_heart', methods=['POST'])
46118
def add_heart(app_id):
47119
uid = get_uid()

0 commit comments

Comments
 (0)