Skip to content

Commit

Permalink
merge: pull 'main' updates into PR branch #635
Browse files Browse the repository at this point in the history
  • Loading branch information
rmlibre committed Oct 4, 2024
2 parents 7761f1d + a8bdbec commit 6ae0133
Show file tree
Hide file tree
Showing 43 changed files with 3,484 additions and 294 deletions.
2 changes: 2 additions & 0 deletions .env.stripe-sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
STRIPE_SECRET_KEY=sk_test_changeme
STRIPE_WEBHOOK_SECRET=whsec_changeme
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,21 @@ jobs:
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max

deploy:
runs-on: ubuntu-latest
needs: build-and-push
steps:
- name: Install doctl and authenticate
run: |
sudo snap install doctl jq
doctl auth init -t $DIGITALOCEAN_TOKEN
env:
DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}

- name: Trigger deployment on DigitalOcean App Platform
run: |
app_id=$(doctl apps list --output json| jq '.[] | select(.spec.name == "hushline-staging") | .id' -r)
doctl apps create-deployment $app_id --force-rebuild --wait
env:
DIGITALOCEAN_APP_ID: ${{ secrets.DIGITALOCEAN_APP_ID }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ hushline/static/data/users_directory.json
/node_modules
.coverage
htmlcov
.env.stripe
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ coverage
hushline/static/vendor/*
hushline/templates/*
.pytest_cache
.venv
3 changes: 3 additions & 0 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ ENV PYTHONPATH=/app
COPY pyproject.toml poetry.lock .

RUN poetry install

ENV FLASK_APP="hushline"
CMD ["./scripts/dev_start.sh"]
2 changes: 1 addition & 1 deletion Dockerfile.prod
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ EXPOSE 8080

# Run!
ENV FLASK_APP="hushline"
CMD ["./start.sh"]
CMD ["./scripts/prod_start.sh"]
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ migrate-dev: ## Run dev env migrations
migrate-prod: ## Run prod env (alembic) migrations
poetry run flask db upgrade

.PHONY: dev-data
dev-data: migrate-dev ## Run dev env migrations, and add dev data
poetry run ./scripts/dev_data.py

.PHONY: lint
lint: ## Lint the code
poetry run ruff format --check && \
Expand Down
6 changes: 6 additions & 0 deletions dev_env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ export SQLALCHEMY_DATABASE_URI=postgresql://hushline:[email protected]:5432/hus
export REGISTRATION_CODES_REQUIRED=False
export SESSION_COOKIE_NAME=session
export [email protected]

# Stripe
export STRIPE_PUBLISHABLE_KEY=pk_test_51OhDeALcBPqjxU07I70UA6JYGDPUmkxEwZW0lvGyNXGlJ4QPfWIBFZJau7XOb3QDzDWrVutBVkz9SNrSjq2vRawm00TwfyFuma
# set these manually:
# export STRIPE_SECRET_KEY=
# export STRIPE_WEBHOOK_SECRET=
49 changes: 49 additions & 0 deletions docker-compose.stripe.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
services:
app: &app_env
build:
context: .
dockerfile: Dockerfile.dev
ports:
- 127.0.0.1:8080:8080
environment:
FLASK_APP: hushline
FLASK_ENV: development
ENCRYPTION_KEY: bi5FDwhZGKfc4urLJ_ChGtIAaOPgxd3RDOhnvct10mw=
SECRET_KEY: cb3f4afde364bfb3956b97ca22ef4d2b593d9d980a4330686267cabcd2c0befd
SQLALCHEMY_DATABASE_URI: postgresql://hushline:hushline@postgres:5432/hushline
REGISTRATION_CODES_REQUIRED: False
SESSION_COOKIE_NAME: session
NOTIFICATIONS_ADDRESS: [email protected]
env_file:
- .env.stripe
volumes:
- ./:/app
depends_on:
- dev_data
restart: always

worker:
<<: *app_env
ports: []
restart: always
command: poetry run flask stripe start-worker
depends_on:
- app

dev_data:
<<: *app_env
ports: []
restart: on-failure
command: make dev-data
depends_on:
- postgres

postgres:
image: postgres:16.4-alpine3.20
environment:
POSTGRES_USER: hushline
POSTGRES_PASSWORD: hushline
POSTGRES_DB: hushline
ports:
- 127.0.0.1:5432:5432
9 changes: 6 additions & 3 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,22 @@ services:
volumes:
- ./:/app
depends_on:
- postgres
- dev_data
restart: always
command: poetry run flask run --debug --host=0.0.0.0 --port=8080 --with-threads

dev_data:
<<: *app_env
ports: []
restart: on-failure
command: make migrate-dev && ./scripts/dev_data.py
command: make dev-data
depends_on:
- postgres

postgres:
image: postgres:16.4-alpine3.20
environment:
POSTGRES_USER: hushline
POSTGRES_PASSWORD: hushline
POSTGRES_DB: hushline
ports:
- 127.0.0.1:5432:5432
64 changes: 62 additions & 2 deletions hushline/__init__.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import asyncio
import logging
import os
from datetime import timedelta
from typing import Any

from flask import Flask, flash, redirect, request, session, url_for
from flask.cli import AppGroup
from flask_migrate import Migrate
from jinja2 import StrictUndefined
from sqlalchemy.exc import ProgrammingError
from werkzeug.middleware.proxy_fix import ProxyFix
from werkzeug.wrappers.response import Response

from . import admin, routes, settings
from . import admin, premium, routes, settings
from .db import db
from .model import HostOrganization, User
from .model import HostOrganization, Tier, User
from .version import __version__


Expand Down Expand Up @@ -58,6 +60,9 @@ def create_app() -> Flask:
app.config["SMTP_PASSWORD"] = os.environ.get("SMTP_PASSWORD", None)
app.config["SMTP_ENCRYPTION"] = os.environ.get("SMTP_ENCRYPTION", "StartTLS")
app.config["REQUIRE_PGP"] = os.environ.get("REQUIRE_PGP", "False").lower() == "true"
app.config["STRIPE_PUBLISHABLE_KEY"] = os.environ.get("STRIPE_PUBLISHABLE_KEY", None)
app.config["STRIPE_SECRET_KEY"] = os.environ.get("STRIPE_SECRET_KEY", None)
app.config["STRIPE_WEBHOOK_SECRET"] = os.environ.get("STRIPE_WEBHOOK_SECRET", None)

# Handle the tips domain for profile verification
app.config["SERVER_NAME"] = os.getenv("SERVER_NAME")
Expand All @@ -78,10 +83,18 @@ def create_app() -> Flask:
db.init_app(app)
Migrate(app, db)

# Initialize Stripe
if app.config["STRIPE_SECRET_KEY"]:
with app.app_context():
premium.init_stripe()

routes.init_app(app)
for module in [admin, settings]:
app.register_blueprint(module.create_blueprint())

if app.config["STRIPE_SECRET_KEY"]:
app.register_blueprint(premium.create_blueprint(app))

@app.errorhandler(404)
def page_not_found(e: Exception) -> Response:
flash("⛓️‍💥 That page doesn't exist.", "warning")
Expand All @@ -102,6 +115,10 @@ def inject_host() -> dict[str, HostOrganization]:
def inject_is_personal_server() -> dict[str, Any]:
return {"is_personal_server": app.config["IS_PERSONAL_SERVER"]}

@app.context_processor
def inject_is_premium_enabled() -> dict[str, Any]:
return {"is_premium_enabled": bool(app.config.get("STRIPE_SECRET_KEY", False))}

# Add Onion-Location header to all responses
if app.config["ONION_HOSTNAME"]:

Expand All @@ -112,6 +129,9 @@ def add_onion_location_header(response: Response) -> Response:
)
return response

# Register custom CLI commands
register_commands(app)

# we can't
if app.config.get("FLASK_ENV", None) != "development":
with app.app_context():
Expand All @@ -126,3 +146,43 @@ def add_onion_location_header(response: Response) -> Response:
app.logger.warning("HostOrganization data not found in database.")

return app


def register_commands(app: Flask) -> None:
stripe_cli = AppGroup("stripe")

@stripe_cli.command("configure")
def configure() -> None:
"""Configure Stripe and premium tiers"""
# Make sure tiers exist
with app.app_context():
free_tier = Tier.free_tier()
if not free_tier:
free_tier = Tier(name="Free", monthly_amount=0)
db.session.add(free_tier)
db.session.commit()
business_tier = Tier.business_tier()
if not business_tier:
business_tier = Tier(name="Business", monthly_amount=2000)
db.session.add(business_tier)
db.session.commit()

# Configure Stripe
if app.config["STRIPE_SECRET_KEY"]:
with app.app_context():
premium.init_stripe()
premium.create_products_and_prices()
else:
app.logger.info("Skipping Stripe configuration because STRIPE_SECRET_KEY is not set")

@stripe_cli.command("start-worker")
def start_worker() -> None:
"""Start the Stripe worker"""
if not app.config["STRIPE_SECRET_KEY"]:
app.logger.error("Cannot start the Stripe worker without a STRIPE_SECRET_KEY")
return

with app.app_context():
asyncio.run(premium.worker(app))

app.cli.add_command(stripe_cli)
38 changes: 36 additions & 2 deletions hushline/admin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from flask import Blueprint, abort, flash, redirect, url_for
from flask import Blueprint, abort, flash, redirect, request, url_for
from werkzeug.wrappers.response import Response

from .db import db
from .model import User
from .model import Tier, User
from .premium import update_price
from .utils import admin_authentication_required


Expand Down Expand Up @@ -31,4 +32,37 @@ def toggle_admin(user_id: int) -> Response:
flash("✅ User admin status toggled.", "success")
return redirect(url_for("settings.index"))

@bp.route("/update_tier/<int:tier_id>", methods=["POST"])
@admin_authentication_required
def update_tier(tier_id: int) -> Response:
tier = db.session.get(Tier, tier_id)
if tier is None:
abort(404)

# Get monthly_price from the request
monthly_price = request.form.get("monthly_price")
if not monthly_price:
flash("❌ Monthly price is required.", "danger")
return redirect(url_for("settings.index"))

# Convert the monthly_price to a float
try:
monthly_price_number = float(monthly_price)
except ValueError:
flash("❌ Monthly price must be a number.", "danger")
return redirect(url_for("settings.index"))

# Convert to cents
monthly_amount = int(monthly_price_number * 100)

# Update in the database
tier.monthly_amount = monthly_amount
db.session.commit()

# Update in stripe
update_price(tier)

flash("✅ Price updated.", "success")
return redirect(url_for("settings.index"))

return bp
Loading

0 comments on commit 6ae0133

Please sign in to comment.