Files
StormRunner06106 6e3779e500 Banners, additional images, and files upload to Dropbox.
Dropbox file IDs are saved with the article in Supabase.
Banners appear in thumbnails and behind the article title/subtitle.
Additional images and files appear below the article text.
2026-09-13 08:58:09 -07:00

65 lines
3.0 KiB
Python

"""Run python -m backend.connect_dropbox to authorize the owner's Dropbox once."""
import getpass
import os
import webbrowser
from pathlib import Path
from dotenv import dotenv_values, set_key
from dropbox import DropboxOAuth2FlowNoRedirect
REQUIRED_SCOPES = frozenset({"files.content.write", "files.content.read"})
def main():
env_path = Path(__file__).with_name(".env")
# Setup edits this file, so a stale shell variable must not select another app.
settings = {**os.environ, **dotenv_values(env_path)}
key = (settings.get("DROPBOX_APP_KEY") or "").strip()
secret = (settings.get("DROPBOX_APP_SECRET") or "").strip()
if not key or not secret:
raise SystemExit("Add DROPBOX_APP_KEY and DROPBOX_APP_SECRET to backend/.env first. Create the app at https://www.dropbox.com/developers/apps (App Folder access).")
flow = DropboxOAuth2FlowNoRedirect(
key, secret, token_access_type="offline",
# Omitting scope asks Dropbox for the permissions saved in App Console.
# Check the actual grant below before persisting credentials.
timeout=30,
)
url = flow.start()
print(f"Using App key: {key} (compare with your Dropbox app's Settings tab).")
print("Requesting the permissions saved in that app's Permissions tab.")
print("Enable files.content.read and files.content.write there, then click Submit.")
print("Opening Dropbox. Review the permissions and approve using the owner's account.")
print("If the browser does not open, copy this entire URL:")
print(url)
try:
webbrowser.open(url)
except webbrowser.Error:
pass # The printed URL also works on servers without a browser.
code = getpass.getpass("Paste the authorization code here (hidden): ").strip()
if not code:
raise SystemExit("No authorization code entered; no credentials were saved.")
try:
result = flow.finish(code)
except Exception as exc:
raise SystemExit(f"Dropbox authorization failed ({type(exc).__name__}). Try again; no credentials were saved.") from None
if not result.refresh_token:
raise SystemExit("Dropbox did not issue a refresh token. Run setup again.")
granted_scopes = result.scope or []
if isinstance(granted_scopes, str):
granted_scopes = granted_scopes.split()
missing = REQUIRED_SCOPES - set(granted_scopes)
if missing:
raise SystemExit(
"Dropbox authorization succeeded, but the app did not grant: "
+ ", ".join(sorted(missing))
+ f". In App Console, select the app with App key {key}, enable these "
"permissions, click Submit, and rerun setup. No credentials were saved."
)
set_key(str(env_path), "DROPBOX_REFRESH_TOKEN", result.refresh_token)
set_key(str(env_path), "JOURNAL_MEDIA_STORAGE", "dropbox")
print("Dropbox connected. Refresh token saved in backend/.env. Restart FastAPI, then migrate existing article media if needed.")
if __name__ == "__main__":
main()