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