Browse Source

Release v1.1.0 - Portal app download system + OTB Cloud integration

main v1.1.0
def 3 days ago
parent
commit
a386c70743
  1. 8
      PROJECT_STATE.md
  2. 5
      README.md
  3. 2
      VERSION
  4. 42
      backend/routes/portal_services.py
  5. 22
      bump.sh
  6. 1
      downloads/apps/otb-cloud/latest.apk
  7. BIN
      downloads/apps/otb-cloud/otb-cloud-v0.3.3.apk
  8. 10
      templates/portal/services_here.html

8
PROJECT_STATE.md

@ -92,3 +92,11 @@ Infrastructure:
- Improved UX: no manual code entry required - Improved UX: no manual code entry required
- Portal onboarding now production-ready - Portal onboarding now production-ready
## v1.1.0 - 2026-05-03
- Added portal-based Android app download system
- Secure APK delivery through OTB Billing (authenticated route)
- "Download Android App" button added to OTB Cloud service card
- Centralized app distribution strategy established
- Foundation laid for future Follow-me and additional app downloads

5
README.md

@ -1,3 +1,8 @@
## v1.1.0 (2026-05-03)
- Portal-based Android app downloads
- Secure APK delivery via authenticated route
- OTB Cloud card now includes app download button
## v1.0.0 (2026-05-03) ## v1.0.0 (2026-05-03)
- Clickable portal invite links - Clickable portal invite links
- Direct account activation from email - Direct account activation from email

2
VERSION

@ -1 +1 @@
v1.0.0 v1.1.0

42
backend/routes/portal_services.py

@ -1,4 +1,5 @@
from flask import Blueprint, render_template, session, redirect, url_for, flash from pathlib import Path
from flask import send_file, abort, Blueprint, render_template, session, redirect, url_for, flash
import hmac, hashlib, time, urllib.parse, os import hmac, hashlib, time, urllib.parse, os
OTB_PORTAL_SHARED_SECRET = os.getenv("OTB_PORTAL_SHARED_SECRET", "!2Eas678") OTB_PORTAL_SHARED_SECRET = os.getenv("OTB_PORTAL_SHARED_SECRET", "!2Eas678")
@ -115,3 +116,42 @@ def portal_launch_otb_cloud():
url = build_otb_cloud_handoff_url(uid, email) url = build_otb_cloud_handoff_url(uid, email)
return redirect(url) return redirect(url)
@portal_services_bp.route("/portal/downloads/apps/<app_slug>/latest")
def portal_download_latest_app(app_slug):
client = get_portal_client()
if not client:
return redirect("/portal")
allowed_apps = {
"otb-cloud": {
"filename": "latest.apk",
"download_name": "otb-cloud-latest.apk",
},
"follow-me": {
"filename": "latest.apk",
"download_name": "follow-me-latest.apk",
},
}
if app_slug not in allowed_apps:
abort(404)
base_dir = Path(__file__).resolve().parents[2] / "downloads" / "apps" / app_slug
file_path = (base_dir / allowed_apps[app_slug]["filename"]).resolve()
try:
if base_dir.resolve() not in file_path.parents:
abort(403)
if not file_path.exists() or not file_path.is_file():
abort(404)
except Exception:
abort(404)
return send_file(
str(file_path),
as_attachment=True,
download_name=allowed_apps[app_slug]["download_name"],
mimetype="application/vnd.android.package-archive",
)

22
bump.sh

@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e set -e
VERSION="v0.3.0" VERSION="v1.1.0"
DATE="$(date +%Y-%m-%d)" DATE="$(date +%Y-%m-%d)"
STAMP="$(date +%Y%m%d-%H%M%S)" STAMP="$(date +%Y%m%d-%H%M%S)"
@ -20,25 +20,25 @@ echo "===== UPDATE PROJECT_STATE.md ====="
cat <<STATE >> PROJECT_STATE.md cat <<STATE >> PROJECT_STATE.md
## $VERSION - $DATE ## $VERSION - $DATE
- Portal onboarding flow upgraded - Added portal-based Android app download system
- Email invites now include clickable activation link - Secure APK delivery through OTB Billing (authenticated route)
- /portal/set-password now supports direct email+code login - "Download Android App" button added to OTB Cloud service card
- Auto session creation from invite link - Centralized app distribution strategy established
- Improved UX: no manual code entry required - Foundation laid for future Follow-me and additional app downloads
- Portal onboarding now production-ready
STATE STATE
echo "===== UPDATE README.md =====" echo "===== UPDATE README.md ====="
sed -i "1i\\ sed -i "1i\\
## $VERSION ($DATE)\\ ## $VERSION ($DATE)\\
- Clickable portal invite links\\ - Portal-based Android app downloads\\
- Direct account activation from email\\ - Secure APK delivery via authenticated route\\
- Improved onboarding UX\\ - OTB Cloud card now includes app download button\\
" README.md " README.md
echo "===== VERIFY PYTHON =====" echo "===== VERIFY PYTHON ====="
python3 -m py_compile backend/app.py python3 -m py_compile backend/app.py
python3 -m py_compile backend/routes/portal_services.py
echo "===== CREATE FULL BACKUP =====" echo "===== CREATE FULL BACKUP ====="
zip -r "/home/def/backuphere/otb_billing-$VERSION-$STAMP.zip" . >/dev/null zip -r "/home/def/backuphere/otb_billing-$VERSION-$STAMP.zip" . >/dev/null
@ -47,7 +47,7 @@ echo "===== GIT ADD ====="
git add . git add .
echo "===== GIT COMMIT =====" echo "===== GIT COMMIT ====="
git commit -m "Release $VERSION - Portal onboarding flow complete (email link activation)" git commit -m "Release $VERSION - Portal app download system + OTB Cloud integration"
echo "===== GIT TAG =====" echo "===== GIT TAG ====="
git tag "$VERSION" git tag "$VERSION"

1
downloads/apps/otb-cloud/latest.apk

@ -0,0 +1 @@
otb-cloud-v0.3.3.apk

BIN
downloads/apps/otb-cloud/otb-cloud-v0.3.3.apk

Binary file not shown.

10
templates/portal/services_here.html

@ -45,10 +45,16 @@
</div> </div>
</div> </div>
<div class="service-card-actions"> <div class="service-card-actions" style="display:flex;gap:10px;flex-wrap:wrap;">
{% if service.enabled %} {% if service.enabled %}
<a class="portal-btn primary" href="{{ service.href }}" <a class="portal-btn primary" href="{{ service.href }}"
{% if service.href.startswith('http') %}target="_blank" rel="noopener noreferrer"{% endif %}>{{ service.button_text }}</a> {% if service.href.startswith('http') %}target="_blank" rel="noopener noreferrer"{% endif %}>{{ service.button_text }}</a>
{% if service.name == "OTB Cloud Backup & Storage" %}
<a class="portal-btn" href="/portal/downloads/apps/otb-cloud/latest">
Download Android App
</a>
{% endif %}
{% else %} {% else %}
<button class="portal-btn btn-disabled" disabled>{{ service.button_text }}</button> <button class="portal-btn btn-disabled" disabled>{{ service.button_text }}</button>
{% endif %} {% endif %}
@ -57,6 +63,8 @@
{% endfor %} {% endfor %}
</section> </section>
<style> <style>
.services-grid { .services-grid {
display: grid; display: grid;

Loading…
Cancel
Save