Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
1e02685
fix: critical backend fixes — duplicate enums, missing jobs, health c…
Bali0531-RC Feb 23, 2026
9408b1e
perf: fix N+1 queries in addon listing endpoints (#2)
Bali0531-RC Feb 23, 2026
66dc2da
fix: frontend routing fixes — React Router navigation, 404 page, copy…
Bali0531-RC Feb 23, 2026
457baa0
fix: OAuth cleanup, type dedup, PayPal config, remove unused deps (#4)
Bali0531-RC Feb 23, 2026
cbde9dc
feat: addon icon, README markdown, download count display (#6)
Bali0531-RC Feb 23, 2026
295ffdf
feat: mobile responsive hamburger menu (#7)
Bali0531-RC Feb 23, 2026
e9ddca4
feat: add star/favorite system, reviews & ratings, and trending addon…
Bali0531-RC Feb 23, 2026
b402c32
fix: correct migration 008 down_revision from 006 to 007
Bali0531-RC Feb 23, 2026
b07d924
feat: server-side search, sort, tag filter and pagination for addons …
Bali0531-RC Feb 23, 2026
a4b66c6
feat: screenshots, banner, categories page, and notification system (#9)
Bali0531-RC Feb 23, 2026
ba27def
Fix #1
Bali0531-RC Feb 23, 2026
b578bb8
Fix #2
Bali0531-RC Feb 23, 2026
a753083
fix: correct migration 007 down_revision to match 006_add_api_keys
Bali0531-RC Feb 23, 2026
17abafd
fix: cast JSON tags to text for LIKE filtering, remove duplicate tag …
Bali0531-RC Feb 23, 2026
105f0cb
fix: correct unique user calculation to use actual distinct counts
Bali0531-RC Feb 23, 2026
d1cd131
feat: PRO version management - release channels, deprecation, rollback
Bali0531-RC Feb 23, 2026
a64c138
feat(PRO-1, PRO-9): Advanced analytics with date range, export, and A…
Bali0531-RC Feb 23, 2026
9c11cf4
feat(PRO-6, PRO-7): Addon collaborators and ownership transfer
Bali0531-RC Feb 23, 2026
53b1bb9
feat: add addon page theme customization (PRO-10) and verified develo…
Bali0531-RC Feb 23, 2026
5e83fcd
Merge fix/analytics-unique-users into dev (PR #10)
Bali0531-RC Feb 23, 2026
0891010
Merge feat/pro-version-management into dev (PR #11)
Bali0531-RC Feb 23, 2026
c7722cd
Merge feat/pro-advanced-analytics into dev (PR #12)
Bali0531-RC Feb 23, 2026
930d44b
Merge feat/pro-collaboration into dev (PR #13)
Bali0531-RC Feb 23, 2026
b52e984
Merge feat/pro-developer-profile into dev (PR #14)
Bali0531-RC Feb 23, 2026
c500ba0
fix: renumber developer profile migration to 012 after merge
Bali0531-RC Feb 23, 2026
f0defd8
fix: add is_verified_developer to listPublicUsers API return type
Bali0531-RC Feb 23, 2026
6cd6825
fix: use create_type=False and sa.text for enum default in migration 010
Bali0531-RC Feb 23, 2026
682238e
fix: use raw SQL for enum creation and column add in migration 010
Bali0531-RC Feb 23, 2026
8d53fcd
fix: handle pre-existing releasechannel enum type in migration 010
Bali0531-RC Feb 23, 2026
ff93b1b
fix: drop and recreate releasechannel enum in migration 010
Bali0531-RC Feb 23, 2026
0c01e03
fix: make migrations 011 and 012 idempotent
Bali0531-RC Feb 23, 2026
cda0aeb
fix: use lowercase enum values in SQLAlchemy model columns
Bali0531-RC Feb 23, 2026
4c9b712
fix: trending endpoint IndexError and add missing favicon.svg
Bali0531-RC Feb 23, 2026
fdb7cfb
feat(PREM-10/11/12): Webhook Enhancements (#15)
Bali0531-RC Feb 23, 2026
85b8115
feat: Organization Enhancements (PREM-1) (#16)
Bali0531-RC Feb 23, 2026
e745b27
feat: Staged Rollouts & Feature Flags (PREM-2/3/4) (#17)
Bali0531-RC Feb 23, 2026
0f26753
feat: Security Suite (PREM-5/6/7/8/9) (#18)
Bali0531-RC Feb 23, 2026
3cdea50
feat: Premium Analytics Suite (PREM-15/16/17/18/19) (#19)
Bali0531-RC Feb 23, 2026
b4a858d
feat: Marketplace & Sponsorship (PREM-13/14) (#20)
Bali0531-RC Feb 23, 2026
f6ef280
infra: Database Optimization & Redis Caching (INFRA-2/4/5) (#21)
Bali0531-RC Feb 23, 2026
836bbd3
infra: Structured Logging & Webhook Rate Limiting (INFRA-6/9) (#22)
Bali0531-RC Feb 23, 2026
3bf8cf8
feat(infra): Backend Test Suite — 99 tests (INFRA-10) (#23)
Bali0531-RC Feb 23, 2026
1c67bc0
feat(infra): Frontend Test Suite — 25 tests with Vitest (INFRA-11) (#24)
Bali0531-RC Feb 23, 2026
300a5a7
feat(infra): CI/CD Pipeline, Backup, Task Queue, CDN Headers (INFRA-3…
Bali0531-RC Feb 23, 2026
6063174
feat(infra): DX tooling — seed data, E2E config, OpenAPI client gen (…
Bali0531-RC Feb 23, 2026
b96c7b7
fix: renumber duplicate 013_ alembic migrations to sequential 013-019…
Bali0531-RC Feb 23, 2026
e74498b
fix: bump pytest to 8.2.0 for pytest-asyncio 0.24.0 compatibility
Bali0531-RC Feb 23, 2026
a0f8157
fix: resolve TypeScript syntax errors from broken merge conflict reso…
Bali0531-RC Feb 23, 2026
e6d4bd0
fix: remove unused vi import from api.test.ts
Bali0531-RC Feb 23, 2026
54e4c26
fix: resolve Python syntax errors from broken merge conflict resolution
Bali0531-RC Feb 23, 2026
7c987a1
fix: resolve DuplicateObject error in alembic migrations 015/016
Bali0531-RC Feb 23, 2026
46204db
fix: resolve GROUP BY error in hourly breakdown query
Bali0531-RC Feb 23, 2026
a6c5028
feat: add marketplace UI to public pages
Bali0531-RC Feb 23, 2026
5888061
version push
Bali0531-RC Feb 23, 2026
54bff81
fix: include marketplace fields in addon API responses
Bali0531-RC Feb 23, 2026
ac937d3
fix: show Admin role badge when user is promoted to admin
Bali0531-RC Feb 23, 2026
b2dbef6
fix: allow disabling paid pricing (make price_cents optional)
Bali0531-RC Feb 23, 2026
61d8346
fix: invalidate cache on marketplace updates + center verified badge
Bali0531-RC Feb 23, 2026
82bfea7
fix: prevent browser caching of API responses
Bali0531-RC Feb 23, 2026
3365410
debug: log sponsor_url from API response
Bali0531-RC Feb 23, 2026
12f72a9
debug: add render-level logging for sponsor_url
Bali0531-RC Feb 23, 2026
a7c24b0
fix: rename sponsor link to avoid ad blocker filter lists
Bali0531-RC Feb 23, 2026
3bee0a9
feat: implement Stripe Checkout for paid addon purchases with Connect…
Bali0531-RC Feb 23, 2026
553c4ef
fix: gate paid addon downloads behind license check
Bali0531-RC Feb 23, 2026
e996867
fix: only pass pool kwargs to non-SQLite engines
Bali0531-RC Feb 23, 2026
e0e060e
fix: add proper .gitmodules for version-checker/pavc submodule
Bali0531-RC Feb 23, 2026
15cdbe9
Fix remaining audit items (BUG-2, BUG-10, BUG-14, FREE-1, PRO-2, PRO-…
Bali0531-RC Feb 24, 2026
c69085d
Fix migration 023: avoid duplicate experimentstatus enum creation
Bali0531-RC Feb 24, 2026
c7b2b41
fix: rewrite migration 023 to use raw SQL instead of sa.Enum
Bali0531-RC Feb 24, 2026
eb012cd
fix: categories page empty because per_page=500 exceeds API limit of 100
Bali0531-RC Feb 24, 2026
5b6f98d
fix: CategoryAddons also exceeded per_page=100 limit (was 200)
Bali0531-RC Feb 24, 2026
bc30487
feat: update pricing and subscription feature lists with new features
Bali0531-RC Feb 24, 2026
8a29a39
feat: PRO-8 CI/CD GitHub Actions example + unique client ID analytics
Bali0531-RC Feb 24, 2026
2e045c0
fixes
Bali0531-RC Jun 10, 2026
2698840
ae
Bali0531-RC Jun 10, 2026
42f0730
some fixes
Bali0531-RC Jun 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ INITIAL_ADMIN_DISCORD_ID=your_discord_user_id
EMAIL_ENABLED=true
SMTP_HOST=mail-eu.smtp2go.com
SMTP_PORT=587
SMTP_USERNAME=no-reply@m.plexdev.live
SMTP_USERNAME=no-reply@m.plexdev.xyz
SMTP_PASSWORD=your_smtp_password
SMTP_USE_TLS=true
EMAIL_FROM_ADDRESS=no-reply@m.plexdev.live
EMAIL_FROM_ADDRESS=no-reply@m.plexdev.xyz
EMAIL_FROM_NAME=PlexAddons
ADMIN_NOTIFICATION_EMAIL=contact@plexdev.live
ADMIN_NOTIFICATION_EMAIL=contact@plexdev.xyz

# Discord Bot (separate from OAuth2 - for DM notifications)
DISCORD_BOT_TOKEN=your_discord_bot_token
Expand Down
114 changes: 114 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: CI

on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]

jobs:
backend-lint:
name: Backend Lint
runs-on: ubuntu-latest
defaults:
run:
working-directory: plexaddons-api
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
- run: pip install -r requirements.txt
- name: Syntax check
run: |
python -c "
import ast, pathlib, sys
errors = []
for p in pathlib.Path('app').rglob('*.py'):
try:
ast.parse(p.read_text())
except SyntaxError as e:
errors.append(f'{p}: {e}')
if errors:
for e in errors: print(e)
sys.exit(1)
print(f'All {len(list(pathlib.Path(\"app\").rglob(\"*.py\")))} files OK')
"

backend-test:
name: Backend Tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: plexaddons-api
env:
SECRET_KEY: test-secret-key-ci
DATABASE_URL: sqlite+aiosqlite:///test.db
REDIS_URL: redis://localhost:6379/15
DISCORD_CLIENT_ID: test
DISCORD_CLIENT_SECRET: test
DISCORD_REDIRECT_URI: http://localhost/callback
STRIPE_SECRET_KEY: sk_test_fake
STRIPE_WEBHOOK_SECRET: whsec_test
STRIPE_PRO_PRICE_ID: price_pro
STRIPE_PREMIUM_PRICE_ID: price_premium
PAYPAL_CLIENT_ID: test
PAYPAL_CLIENT_SECRET: test
PAYPAL_WEBHOOK_ID: test
PAYPAL_PRO_PLAN_ID: P-test
PAYPAL_PREMIUM_PLAN_ID: P-test
ENVIRONMENT: testing
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
- run: pip install -r requirements.txt
- run: python -m pytest tests/ -v --tb=short

frontend-lint:
name: Frontend Lint & Build
runs-on: ubuntu-latest
defaults:
run:
working-directory: plexaddons-web
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
cache-dependency-path: plexaddons-web/package-lock.json
- run: npm ci
- run: npx tsc --noEmit
- run: npm run build

frontend-test:
name: Frontend Tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: plexaddons-web
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
cache-dependency-path: plexaddons-web/package-lock.json
- run: npm ci
- run: npm test

docker-build:
name: Docker Build Check
runs-on: ubuntu-latest
needs: [backend-test, frontend-test]
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v4
- name: Build API image
run: docker build -t plexaddons-api:ci plexaddons-api/
- name: Build Web image
run: docker build -t plexaddons-web:ci plexaddons-web/
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,7 @@ secrets/
# Coverage
.coverage
htmlcov/

# Audit
AUDIT_FINDINGS.txt
.pytest_cache/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "version-checker/pavc"]
path = version-checker/pavc
url = https://github.com/Bali0531-RC/pavc
8 changes: 3 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3.8'

services:
api:
build:
Expand All @@ -26,7 +24,7 @@ services:
- PAYPAL_PRO_PLAN_ID=${PAYPAL_PRO_PLAN_ID}
- PAYPAL_PREMIUM_PLAN_ID=${PAYPAL_PREMIUM_PLAN_ID}
- INITIAL_ADMIN_DISCORD_ID=${INITIAL_ADMIN_DISCORD_ID}
- FRONTEND_URL=${FRONTEND_URL:-https://addons.plexdev.live}
- FRONTEND_URL=${FRONTEND_URL:-https://addons.plexdev.xyz}
- ENVIRONMENT=${ENVIRONMENT:-production}
# Email (SMTP)
- EMAIL_ENABLED=${EMAIL_ENABLED:-true}
Expand All @@ -35,7 +33,7 @@ services:
- SMTP_USERNAME=${SMTP_USERNAME}
- SMTP_PASSWORD=${SMTP_PASSWORD}
- SMTP_USE_TLS=${SMTP_USE_TLS:-true}
- EMAIL_FROM_ADDRESS=${EMAIL_FROM_ADDRESS:-no-reply@m.plexdev.live}
- EMAIL_FROM_ADDRESS=${EMAIL_FROM_ADDRESS:-no-reply@m.plexdev.xyz}
- EMAIL_FROM_NAME=${EMAIL_FROM_NAME:-PlexAddons}
- ADMIN_NOTIFICATION_EMAIL=${ADMIN_NOTIFICATION_EMAIL}
# Discord Bot (for DM notifications)
Expand Down Expand Up @@ -67,7 +65,7 @@ services:
ports:
- "3310:80"
environment:
- VITE_API_URL=${API_URL:-https://addons.plexdev.live/api}
- VITE_API_URL=${API_URL:-https://addons.plexdev.xyz/api}
- VITE_DISCORD_CLIENT_ID=${DISCORD_CLIENT_ID}
- VITE_STRIPE_PUBLISHABLE_KEY=${STRIPE_PUBLISHABLE_KEY}
- VITE_PAYPAL_CLIENT_ID=${PAYPAL_CLIENT_ID}
Expand Down
167 changes: 167 additions & 0 deletions examples/.github/workflows/publish-addon.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# PlexAddons CI/CD - Publish Addon Version
#
# This workflow automatically publishes a new version of your addon
# to addons.plexdev.xyz when you create a GitHub release or push a tag.
#
# Prerequisites:
# 1. Create an API key at https://addons.plexdev.xyz/dashboard/api-keys
# - Required scope: "versions:write"
# - Pro tier: up to 3 keys | Premium tier: up to 10 keys
# 2. Add the API key as a repository secret named PLEXADDONS_API_KEY
# - Go to Settings → Secrets and variables → Actions → New repository secret
# 3. Your addon must already exist on addons.plexdev.xyz
#
# Usage:
# Option A: Triggered on GitHub Release
# - Create a release in GitHub with a tag like "v1.2.3"
# - The workflow extracts the version from the tag and publishes it
#
# Option B: Triggered on tag push
# - Push a tag: git tag v1.2.3 && git push origin v1.2.3
#
# Customize the env variables below for your addon.

name: Publish to PlexAddons

on:
release:
types: [published]
push:
tags:
- 'v*'

env:
# ─── CONFIGURE THESE ───────────────────────────────────────────
ADDON_SLUG: "my-addon" # Your addon's slug on addons.plexdev.xyz
DOWNLOAD_URL_TEMPLATE: "https://github.com/${{ github.repository }}/releases/download/${TAG}/addon.zip"
# Optional:
CHANNEL: "stable" # stable | beta | alpha | canary (Pro+ feature)
ROLLOUT_PERCENTAGE: "100" # 0-100, gradual rollout (Premium feature)
# ────────────────────────────────────────────────────────────────

jobs:
publish:
name: Publish Version
runs-on: ubuntu-latest
# Only run once if both release and tag events fire
if: github.event_name == 'release' || !startsWith(github.ref, 'refs/tags/')

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for changelog

- name: Extract version from tag
id: version
run: |
TAG="${GITHUB_REF#refs/tags/}"
VERSION="${TAG#v}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "📦 Publishing version: $VERSION"

- name: Build download URL
id: url
run: |
TAG="${{ steps.version.outputs.tag }}"
# Replace ${TAG} placeholder in the template
URL=$(echo "$DOWNLOAD_URL_TEMPLATE" | sed "s/\${TAG}/$TAG/g")
echo "download_url=$URL" >> "$GITHUB_OUTPUT"
echo "🔗 Download URL: $URL"

- name: Generate changelog from commits
id: changelog
run: |
# Get commits since the previous tag
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [ -n "$PREV_TAG" ]; then
CHANGES=$(git log --pretty=format:"- %s" "$PREV_TAG"..HEAD 2>/dev/null || echo "")
else
CHANGES=$(git log --pretty=format:"- %s" -20 2>/dev/null || echo "")
fi

# Use release body if available, otherwise use commit log
if [ "${{ github.event_name }}" = "release" ] && [ -n "${{ github.event.release.body }}" ]; then
CHANGELOG="${{ github.event.release.body }}"
else
CHANGELOG="$CHANGES"
fi

# Write to file to handle multiline
echo "$CHANGELOG" > /tmp/changelog.txt
echo "has_changelog=true" >> "$GITHUB_OUTPUT"

- name: Publish to PlexAddons
env:
PLEXADDONS_API_KEY: ${{ secrets.PLEXADDONS_API_KEY }}
run: |
CHANGELOG=$(cat /tmp/changelog.txt)

# Check if this is a breaking or urgent release
BREAKING=false
URGENT=false
if echo "${{ steps.version.outputs.tag }}" | grep -qi "breaking"; then
BREAKING=true
fi
# Mark pre-1.0 major bumps or releases labeled "urgent" as urgent
if [ "${{ github.event_name }}" = "release" ]; then
LABELS='${{ toJson(github.event.release.body) }}'
if echo "$LABELS" | grep -qi "urgent"; then
URGENT=true
fi
fi

# Build the JSON payload
PAYLOAD=$(jq -n \
--arg version "${{ steps.version.outputs.version }}" \
--arg download_url "${{ steps.url.outputs.download_url }}" \
--arg description "Release ${{ steps.version.outputs.tag }}" \
--arg changelog_content "$CHANGELOG" \
--argjson breaking "$BREAKING" \
--argjson urgent "$URGENT" \
--arg channel "$CHANNEL" \
--argjson rollout_percentage "${ROLLOUT_PERCENTAGE}" \
'{
version: $version,
download_url: $download_url,
description: $description,
changelog_content: $changelog_content,
breaking: $breaking,
urgent: $urgent,
channel: $channel,
rollout_percentage: ($rollout_percentage | tonumber)
}')

echo "📤 Publishing to PlexAddons API..."
RESPONSE=$(curl -s -w "\n%{http_code}" \
-X POST \
-H "Content-Type: application/json" \
-H "X-API-Key: $PLEXADDONS_API_KEY" \
-d "$PAYLOAD" \
"https://addons.plexdev.xyz/api/v1/addons/${ADDON_SLUG}/versions")

HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')

if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
echo "✅ Successfully published version ${{ steps.version.outputs.version }}"
echo "$BODY" | jq .
else
echo "❌ Failed to publish (HTTP $HTTP_CODE)"
echo "$BODY" | jq . 2>/dev/null || echo "$BODY"
exit 1
fi

- name: Summary
if: success()
run: |
echo "### ✅ Published to PlexAddons" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "| Field | Value |" >> "$GITHUB_STEP_SUMMARY"
echo "|-------|-------|" >> "$GITHUB_STEP_SUMMARY"
echo "| **Addon** | \`${ADDON_SLUG}\` |" >> "$GITHUB_STEP_SUMMARY"
echo "| **Version** | \`${{ steps.version.outputs.version }}\` |" >> "$GITHUB_STEP_SUMMARY"
echo "| **Channel** | \`${CHANNEL}\` |" >> "$GITHUB_STEP_SUMMARY"
echo "| **Rollout** | ${ROLLOUT_PERCENTAGE}% |" >> "$GITHUB_STEP_SUMMARY"
echo "| **Download** | ${{ steps.url.outputs.download_url }} |" >> "$GITHUB_STEP_SUMMARY"
Loading
Loading