Matthew Raymer
1 year ago
7 changed files with 169 additions and 0 deletions
@ -0,0 +1 @@ |
|||||
|
*~ |
@ -0,0 +1,41 @@ |
|||||
|
# Use an official Python runtime as a parent image |
||||
|
FROM python:3.8-alpine3.18 as builder |
||||
|
|
||||
|
RUN apk update && apk upgrade |
||||
|
RUN apk add --no-cache --virtual .build-deps build-base git |
||||
|
RUN apk add bash libffi-dev tzdata --upgrade --no-cache |
||||
|
|
||||
|
ENV TZ America/New_York |
||||
|
|
||||
|
# Set the working directory in the container to /app |
||||
|
WORKDIR /app |
||||
|
|
||||
|
# Copy the current directory contents into the container at /app |
||||
|
COPY app.py /app |
||||
|
COPY requirements.txt /app |
||||
|
COPY models.py /app |
||||
|
COPY init_db.py /app/init_db.py |
||||
|
|
||||
|
|
||||
|
# Install any needed packages specified in requirements.txt |
||||
|
RUN pip install --no-cache-dir -r requirements.txt |
||||
|
RUN python /app/init_db.py |
||||
|
|
||||
|
RUN apk del .build-deps |
||||
|
|
||||
|
# ---- Production Stage ---- |
||||
|
FROM python:3.8-alpine3.18 as production |
||||
|
|
||||
|
# Create a user to run our application |
||||
|
RUN adduser -D myuser |
||||
|
|
||||
|
# Copy the dependencies and installed packages from the builder image |
||||
|
WORKDIR /app |
||||
|
COPY --from=builder /app /app |
||||
|
COPY --from=builder /usr/local /usr/local |
||||
|
|
||||
|
# Switch to the created user |
||||
|
USER myuser |
||||
|
|
||||
|
# Start gunicorn with the appropriate options |
||||
|
CMD ["gunicorn", "-b", "0.0.0.0:5000", "--workers=3", "app:app"] |
@ -0,0 +1,95 @@ |
|||||
|
from flask import Flask, request, jsonify |
||||
|
from models import db, VAPIDKey, Subscription |
||||
|
from py_vapid import Vapid |
||||
|
from pywebpush import webpush, WebPushException |
||||
|
|
||||
|
import json |
||||
|
import os |
||||
|
|
||||
|
app = Flask(__name__) |
||||
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///webpush.db' |
||||
|
db.init_app(app) |
||||
|
|
||||
|
def generate_and_save_vapid_keys(): |
||||
|
vapid = Vapid() |
||||
|
vapid.generate_keys() |
||||
|
private_key = vapid.get_private_key().to_pem().decode('utf-8').strip() |
||||
|
public_key = vapid.get_public_key().to_pem().decode('utf-8').strip() |
||||
|
|
||||
|
key = VAPIDKey(public_key=public_key, private_key=private_key) |
||||
|
db.session.add(key) |
||||
|
db.session.commit() |
||||
|
|
||||
|
|
||||
|
@app.before_first_request |
||||
|
def initialize(): |
||||
|
if not VAPIDKey.query.first(): |
||||
|
generate_and_save_vapid_keys() |
||||
|
|
||||
|
def send_push_notification(subscription_info, message, vapid_key): |
||||
|
try: |
||||
|
webpush( |
||||
|
subscription_info=subscription_info, |
||||
|
data=json.dumps(message), |
||||
|
vapid_private_key=vapid_key.private_key, |
||||
|
vapid_claims={ |
||||
|
"sub": "mailto:your-email@example.com" |
||||
|
} |
||||
|
) |
||||
|
except WebPushException as e: |
||||
|
print(f"Failed to send notification: {e}") |
||||
|
|
||||
|
|
||||
|
@app.route('/web-push/vapid', methods=['GET']) |
||||
|
def get_vapid(): |
||||
|
key = VAPIDKey.query.first() |
||||
|
if key: |
||||
|
return jsonify(public_key=key.public_key) |
||||
|
return jsonify(error='No VAPID keys found'), 404 |
||||
|
|
||||
|
|
||||
|
@app.route('/web-push/subscribe', methods=['POST']) |
||||
|
def subscribe(): |
||||
|
content = request.json |
||||
|
vapid_key = VAPIDKey.query.first() |
||||
|
|
||||
|
if not vapid_key: |
||||
|
return jsonify(success=False, error="No VAPID keys available"), 500 |
||||
|
|
||||
|
subscription = Subscription(endpoint=content['endpoint'], |
||||
|
p256dh=content['keys']['p256dh'], |
||||
|
auth=content['keys']['auth'], |
||||
|
vapid_key_id=vapid_key.id) |
||||
|
db.session.add(subscription) |
||||
|
db.session.commit() |
||||
|
|
||||
|
subscription_info = { |
||||
|
"endpoint": subscription.endpoint, |
||||
|
"keys": { |
||||
|
"p256dh": subscription.p256dh, |
||||
|
"auth": subscription.auth |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
message = {"title": "Subscription Successful", "body": "Thank you for subscribing!"} |
||||
|
send_push_notification(subscription_info, message, vapid_key) |
||||
|
|
||||
|
return jsonify(success=True) |
||||
|
|
||||
|
|
||||
|
@app.route('/web-push/unsubscribe', methods=['DELETE']) |
||||
|
def unsubscribe(): |
||||
|
content = request.json |
||||
|
endpoint = content['endpoint'] |
||||
|
subscription = Subscription.query.filter_by(endpoint=endpoint).first() |
||||
|
|
||||
|
if subscription: |
||||
|
db.session.delete(subscription) |
||||
|
db.session.commit() |
||||
|
return jsonify(success=True, message="Subscription deleted successfully") |
||||
|
else: |
||||
|
return jsonify(success=False, error="Subscription not found"), 404 |
||||
|
|
||||
|
|
||||
|
if __name__ == '__main__': |
||||
|
app.run(debug=True) |
@ -0,0 +1,3 @@ |
|||||
|
#!/bin/bash |
||||
|
|
||||
|
docker build . -t endorser-push-server:1.0 --no-cache |
@ -0,0 +1,9 @@ |
|||||
|
from models import db |
||||
|
from flask import Flask |
||||
|
|
||||
|
app = Flask(__name__) |
||||
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///webpush.db' |
||||
|
db.init_app(app) |
||||
|
|
||||
|
with app.app_context(): |
||||
|
db.create_all() |
@ -0,0 +1,16 @@ |
|||||
|
from flask_sqlalchemy import SQLAlchemy |
||||
|
|
||||
|
db = SQLAlchemy() |
||||
|
|
||||
|
class VAPIDKey(db.Model): |
||||
|
id = db.Column(db.Integer, primary_key=True) |
||||
|
public_key = db.Column(db.String(255), nullable=False) |
||||
|
private_key = db.Column(db.String(255), nullable=False) |
||||
|
subscriptions = db.relationship('Subscription', backref='vapid_key', lazy=True) |
||||
|
|
||||
|
class Subscription(db.Model): |
||||
|
id = db.Column(db.Integer, primary_key=True) |
||||
|
endpoint = db.Column(db.String(500), nullable=False) |
||||
|
p256dh = db.Column(db.String(255), nullable=False) |
||||
|
auth = db.Column(db.String(255), nullable=False) |
||||
|
vapid_key_id = db.Column(db.Integer, db.ForeignKey('vapid_key.id'), nullable=False) |
@ -0,0 +1,4 @@ |
|||||
|
flask |
||||
|
flask_sqlalchemy |
||||
|
py_vapid |
||||
|
pywebpush |
Loading…
Reference in new issue