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.
65 lines
3.0 KiB
Python
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()
|