Skip to content

Commit 4d869d5

Browse files
Merge pull request #62 from pebble-dev/add_forum_override_option
Add forum override option
2 parents c8ab7a7 + e325fff commit 4d869d5

File tree

3 files changed

+89
-5
lines changed

3 files changed

+89
-5
lines changed

appstore/developer_portal_api.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from .s3 import upload_pbw, upload_asset, get_link_for_archive
1919
from .settings import config
2020
from .discord import audit_log
21+
from .discourse import get_topic_url_for_app, is_valid_topic_url, user_owns_discourse_topic, topic_url_to_id
2122
from . import discord, discourse
2223

2324
parent_app = None
@@ -649,6 +650,47 @@ def new_app_icon(app_id, size):
649650

650651
return jsonify(success=True, id=new_image_id, size=size)
651652

653+
@devportal_api.route("/app/<app_id>/forum", methods=['GET'])
654+
def get_app_forum_url(app_id):
655+
try:
656+
app = App.query.filter(App.id == app_id).one()
657+
except NoResultFound:
658+
return jsonify(error="Unknown app", e="app.notfound"), 404
659+
660+
return jsonify(forum_url=get_topic_url_for_app(app))
661+
662+
@devportal_api.route("/app/<app_id>/forum", methods=['POST'])
663+
def update_app_forum_url(app_id):
664+
try:
665+
req = request.json
666+
except BadRequest:
667+
return jsonify(error="Invalid POST body. Expected JSON", e="body.invalid"), 400
668+
669+
if req is None:
670+
return jsonify(error="Invalid POST body. Expected JSON and 'Content-Type: application/json'", e="request.invalid"), 400
671+
672+
if "new_url" not in req:
673+
return jsonify(error="Missing required field: new_url", e="missing.field.new_url"), 400
674+
675+
try:
676+
app = App.query.filter(App.id == app_id).one()
677+
except NoResultFound:
678+
return jsonify(error="Unknown app", e="app.notfound"), 404
679+
680+
if not is_users_developer_id(app.developer_id):
681+
return jsonify(error="You do not have permission to modify that app", e="permission.denied"), 403
682+
683+
if not is_valid_topic_url(req["new_url"]):
684+
return jsonify(error="Invalid URL for new discourse topic", e="url.invalid"), 400
685+
686+
if not user_owns_discourse_topic(req["new_url"]):
687+
return jsonify(error="You are not the creator of the provided discourse topic", e="url.permissions.missing"), 400
688+
689+
app.discourse_topic_id = topic_url_to_id(req["new_url"])
690+
db.session.commit()
691+
692+
return jsonify(success=True, new_url=app.discourse_topic_id)
693+
652694

653695

654696
@devportal_api.route('/wizard/rename/<developer_id>', methods=['POST'])

appstore/discourse.py

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
from .settings import config
44
from .models import App, db
55
from .discord import random_party_emoji
6-
from .utils import get_app_description, generate_image_url
6+
from .utils import get_app_description, generate_image_url, demand_authed_request
7+
import requests
8+
import json
79

810
PLATFORM_EMOJI = {
911
'aplite': ':pebble-orange:',
@@ -16,6 +18,7 @@
1618

1719
if config['DISCOURSE_API_KEY'] is None:
1820
_client = None
21+
print("== Discourse Integration not configured.")
1922
else:
2023
_client = DiscourseClient(host=f"https://{config['DISCOURSE_HOST']}", api_username=config['DISCOURSE_USER'], api_key=config['DISCOURSE_API_KEY'])
2124

@@ -120,6 +123,45 @@ def announce_new_app(app, is_generated, is_new=True):
120123
""")
121124

122125
def get_topic_url_for_app(app):
123-
if not _client or not app.discourse_topic_id or app.discourse_topic_id == -1:
124-
return None
125-
return f"https://{config['DISCOURSE_HOST']}/t/{app.discourse_topic_id}"
126+
if app.discourse_topic_id > 0:
127+
return f"https://{config['DISCOURSE_HOST']}/t/{app.discourse_topic_id}"
128+
else:
129+
return
130+
131+
def is_valid_topic_url(topic_url):
132+
topic_url = topic_url.lower().strip()
133+
134+
start_string = config['DISCOURSE_HOST'].lower()
135+
if not start_string.startswith("http://"):
136+
start_string = "https://" + start_string
137+
138+
return topic_url.startswith(start_string)
139+
140+
def topic_url_to_id(topic_url):
141+
topic_url = topic_url.lower().strip()
142+
if "?" in topic_url:
143+
topic_url = topic_url[:topic_url.index("?")]
144+
sections = topic_url.split("/")
145+
146+
if len(sections) == 5:
147+
# Short url: https://discourse.example.com/t/12345
148+
return int(sections[4])
149+
else:
150+
# Long url: https://discourse.example.com/t/topic-title/12345
151+
# Long url with page: https://discourse.example.com/t/topic-title/12345/2
152+
return int(sections[5])
153+
154+
def fetch_owner_from_topic_url(topic_url):
155+
#The py client sucks a bit so we'll just call the JSON
156+
topic = requests.request("GET", topic_url + ".json").json()
157+
topic_owner = topic["details"]["created_by"]["username"]
158+
return topic_owner
159+
160+
def user_owns_discourse_topic(discourse_topic_url):
161+
discourse_username = fetch_owner_from_topic_url(discourse_topic_url)
162+
163+
auth_result = demand_authed_request('GET', f"{config['REBBLE_AUTH_URL']}/api/v1/me/pebble/appstore")
164+
me = auth_result.json()
165+
my_username = me["rebble_username"]
166+
167+
return discourse_username == my_username

appstore/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
'AWS_SECRET_KEY': os.environ.get('AWS_SECRET_KEY', None),
2929
'S3_ENDPOINT': os.environ.get('S3_ENDPOINT', None),
3030
'TEST_APP_UUID': os.environ.get('TEST_APP_UUID', None),
31-
'DISCOURSE_USER': os.environ.get('DISCOURSE_USER', 'annedroid'),
31+
'DISCOURSE_USER': os.environ.get('DISCOURSE_USER', 'anne_droid'),
3232
'DISCOURSE_API_KEY': os.environ.get('DISCOURSE_API_KEY', None),
3333
'DISCOURSE_HOST': os.environ.get('DISCOURSE_HOST', f'forum.{domain_root}'),
3434
'DISCOURSE_SHOWCASE_TOPIC_ID': int(os.environ.get('DISCOURSE_SHOWCASE_TOPIC_ID', '3')),

0 commit comments

Comments
 (0)