diff --git a/app.py b/app.py index 54217c9..59b902f 100644 --- a/app.py +++ b/app.py @@ -13,6 +13,7 @@ from werkzeug.datastructures import ImmutableMultiDict, FileStorage from werkzeug.utils import secure_filename import file_manager +import finances import firebase_manager import generic_executor import modpacks_manager @@ -20,7 +21,7 @@ import modpacks_manager app = Flask(__name__) app.config['MAX_CONTENT_LENGTH'] = 16 * 1000 * 1000 * 1000 #15.28MB~ cors = CORS(app, origins="*") -apiBP = Blueprint('apiBP', 'BPapi') +mc_api = Blueprint('mc_api_bp', 'bp_mc_api') def generic_response_maker(status_code: http.HTTPStatus, _message: str = None) -> tuple[Response, int]: @@ -50,12 +51,6 @@ def generic_response_maker(status_code: http.HTTPStatus, _message: str = None) - return message, status_code.value -''' -valid, user_id = firebase_manager.verify_jwt_token(data['token']) -TODO : replace 53 by the given statement. -''' - - def authenticate_request(token: str): valid, user_id = firebase_manager.verify_jwt_token(token) if not valid: @@ -102,7 +97,7 @@ route_handlers = { } -@apiBP.route('/', methods=['POST']) +@mc_api.route('/', methods=['POST']) def dynamic_route_handler(path): if path not in route_handlers: return generic_response_maker(http.HTTPStatus.METHOD_NOT_ALLOWED) @@ -128,7 +123,7 @@ def dynamic_route_handler(path): # [!] This route is specific and has to remain out of the dynamic route handler. -@apiBP.route('/Upload', methods=['POST']) +@mc_api.route('/Upload', methods=['POST']) def upload(): form = request.form token: str or None = request.headers.get('SST') @@ -180,12 +175,12 @@ def upload(): -@app.route('/modpacks', methods=['GET']) +@mc_api.route('/modpacks', methods=['GET']) def get_modpacks(): return modpacks_manager.get_modpacks() -@app.route('/modpacks/image/', methods=['GET']) +@mc_api.route('/modpacks/image/', methods=['GET']) def get_modpack_image(filename): return modpacks_manager.get_modpack_image(filename) @@ -195,7 +190,8 @@ def api_cleanup() -> None: return -app.register_blueprint(apiBP) +app.register_blueprint(mc_api) +app.register_blueprint(finances.payment_api) if __name__ == '__main__': parser = argparse.ArgumentParser(description="Background Scheduler") @@ -211,4 +207,4 @@ if __name__ == '__main__': scheduler.start() run_simple('0.0.0.0', 3000, app, use_debugger=False, use_reloader=False) - api_cleanup() \ No newline at end of file + #api_cleanup() \ No newline at end of file diff --git a/finances.py b/finances.py index d214994..205fbf4 100644 --- a/finances.py +++ b/finances.py @@ -1,13 +1,74 @@ import stripe -from flask import Flask, request, jsonify +from flask import request, jsonify, Blueprint +from stripe import StripeClient -app = Flask(__name__) +import firebase_manager +from firebase_manager import log_exception_to_firestore + +payment_api = Blueprint('payment_api_bp', 'bp_payment_api') -#stripe.api_key = os.getenv("STRIPE_SECRET_KEY") -endpoint_secret = "whsec_2a1cadb771f7acfdeaac6720fdd56d3353cca5d38bdc2ed88336932968531457" +stripe_api_key: str = ("sk_live_51PyIYTP3VLLeb9GlKRfsTMh4olzrJxkT1PwXGlW2G" + "HPUeFZmTFHe5ITI0L2tZPBnJd2lHarQifIV5TMh505KFuL300mkItQuBo") +endpoint_secret: str = "whsec_2a1cadb771f7acfdeaac6720fdd56d3353cca5d38bdc2ed88336932968531457" -@app.route('/finances', methods=['POST']) + +stripe.api_key = stripe_api_key +client: StripeClient = StripeClient(api_key=stripe_api_key) + + +products: [str] = ["", + "price_1Q3Ji5P3VLLeb9GlMOZuafV9", "price_1Q3Ji3P3VLLeb9GlqNStquBo", "price_1Q3Ji1P3VLLeb9Gl5uqH23js"] + + +@payment_api.route('/get-checkout-session', methods=['GET']) +def create_checkout_session(): + try: + permission: int = int(request.headers.get('permission')) + email: str = request.headers.get('email') + except ValueError as err: + print(err) + permission: None = None + email: None = None + + if not permission: + return jsonify(message="No product query provided."), 404 + if not email: + return jsonify(message="No customer email provided."), 404 + + try: + session = stripe.checkout.Session.create( + ui_mode = 'embedded', + line_items=[ + { + 'price': products[permission], + 'quantity': 1, + }, + ], + allow_promotion_codes=True, + mode='subscription', + customer_email= email, + return_url= "https://app.servii.fr" + '/return?session_id={CHECKOUT_SESSION_ID}', + automatic_tax={'enabled': True}, + ) + except Exception as err: + firebase_manager.log_exception_to_firestore(exception=err, user_id="Stripe") + return jsonify(message="Unable to get associated product."), 500 + + return jsonify(clientSecret=session.client_secret), 200 + + +@payment_api.route('/get-session-status', methods=['GET']) +def session_status(): + try: + session = stripe.checkout.Session.retrieve(request.args.get('session_id')) + return jsonify(status=session.status, customer_email=session.customer_details.email), 200 + except Exception as err: + firebase_manager.log_exception_to_firestore(exception=err, user_id="Stripe") + return jsonify(message="Unable to retrieve payment session status."), 500 + + +@payment_api.route('/finances', methods=['POST']) def webhook(): payload = request.get_data(as_text=True) sig_header = request.headers.get('Stripe-Signature') @@ -16,30 +77,61 @@ def webhook(): event = stripe.Webhook.construct_event( payload, sig_header, endpoint_secret ) - except ValueError as e: - print(f"Invalid payload: {e}") - return jsonify({'error': 'Invalid payload'}), 400 - except stripe.error.SignatureVerificationError as e: - print(f"Invalid signature: {e}") - return jsonify({'error': 'Invalid signature'}), 400 + except ValueError as err: + print(f"Invalid payload: {err}") + return jsonify({'message': 'Invalid payload'}), 400 + except stripe.error.SignatureVerificationError as err: + print(f"Invalid signature: {err}") + return jsonify({'message': 'Invalid signature'}), 400 + try: + match event['type']: + case 'customer.subscription.created' | 'customer.subscription.updated': + firestore_metadata: int = extract_firestore_data(event) + user_id: str = extract_user_id(event) + firebase_manager.update_firestore(user_id=user_id, data={"subscription": firestore_metadata}) + + case 'customer.subscription.deleted': + user_id: str = extract_user_id(event) + firebase_manager.update_firestore(user_id=user_id, data={"subscription": 0}) + + return jsonify({'status': 'success'}), 200 + + except Exception as err: + log_exception_to_firestore(exception=err, user_id="Stripe") + return jsonify({'status': 'error'}), 500 - # Handle the event based on its type - match event['type']: - case 'customer.subscription.deleted': - customer = event['data']['object'] - print(event) - print(f"Customer stopped subscription: {customer['id']}") - case 'customer.subscription.created': - print(event) - customer = event['data']['object'] - print(f"Customer started subscription: {customer['id']}") - case 'customer.subscription.updated': - print(event) - customer = event['data']['object'] - print(f"Customer started subscription: {customer['id']}") +def extract_firestore_data(event): + try: + firestore_metadata = ( + event.get('data', {}) + .get('object', {}) + .get('items', {}) + .get('data', [{}])[0] + .get('price', {}) + .get('metadata', {}) + .get('firestore', 0) + ) + return int(firestore_metadata) - return jsonify({'status': 'success'}), 200 + except (IndexError, KeyError, ValueError) as e: + return None -if __name__ == '__main__': - app.run(port=3400) \ No newline at end of file + +def extract_user_id(event): + try: + customer_id = event.get('data', {}).get('object', {}).get('customer', None) + customer = client.customers.retrieve(customer_id) + mail: str = customer.get("email", None) + user_id: str = firebase_manager.get_user_from_mail(mail) + return user_id + + except (IndexError, KeyError) as e: + return None + except Exception as err: + log_exception_to_firestore(exception=err, user_id="Stripe") + return None + + +if __name__ == "__main__": + pass diff --git a/firebase_manager.py b/firebase_manager.py index 8e9ce5c..92f2d5b 100644 --- a/firebase_manager.py +++ b/firebase_manager.py @@ -5,7 +5,8 @@ from typing import Union import firebase_admin import jwt from firebase_admin import auth, credentials, firestore -from google.api_core.exceptions import Aborted, DataLoss, NotFound, OutOfRange, PermissionDenied, ResourceExhausted +from google.api_core.exceptions import Aborted, DataLoss, NotFound, OutOfRange, PermissionDenied, ResourceExhausted, \ + GoogleAPICallError from google.cloud.firestore_v1 import FieldFilter, DocumentReference import file_manager @@ -16,10 +17,35 @@ app = firebase_admin.initialize_app(cred) firestore_database = firestore.client() -def get_user_from_id(user_id): +def get_user_from_id(user_id: str): return auth.get_user(user_id) +def get_user_from_mail(mail: str) -> str or None: + try: + users_ref = firestore_database.collection('users') + query = users_ref.where(filter=FieldFilter(field_path='mail', op_string='==', value=mail)).limit(1).stream() + + user_id = None + for doc in query: + user_id = doc.id + break + + return user_id + + except NotFound: + print("No such document!") + return None + + except GoogleAPICallError as e: + print(f"API call error: {e}") + return None + + except Exception as e: + print(f"An error occurred: {e}") + return None + + def verify_jwt_token(token): try: decoded_token = auth.verify_id_token(token, app=app, check_revoked=True) diff --git a/generic_executor.py b/generic_executor.py index 6ef48ee..fb0666d 100644 --- a/generic_executor.py +++ b/generic_executor.py @@ -188,6 +188,8 @@ def server_create(user: UserRecord, name: str, version: str, framework: str = "p if framework not in allowed_frameworks: return HTTPStatus.METHOD_NOT_ALLOWED, f"Framework {framework} not recognized." user_id = user.uid + if not firebase_manager.get_user_field(user_id, "subscription"): + return HTTPStatus.FORBIDDEN, "You haven't yet subscribed to Servii." server_path: str = f"users/{user_id}/{name}" server_template_path: str = f"servers/{framework}/{version}" try: @@ -234,6 +236,8 @@ def server_delete(name: str, user: UserRecord) -> tuple[HTTPStatus, Union[str, N def server_run(user: UserRecord, name: str) -> tuple[HTTPStatus, Union[str, None]]: user_id = user.uid + if not firebase_manager.get_user_field(user_id, "subscription"): + return HTTPStatus.FORBIDDEN, "You haven't yet subscribed to Servii." mc_manager.set_cooldown(user_id=user_id) try: port: int = firebase_manager.get_server_port(user_id) diff --git a/unit_test.py b/unit_test.py index d473389..6e68483 100644 --- a/unit_test.py +++ b/unit_test.py @@ -2,6 +2,7 @@ import os import shutil import firebase_manager +from firebase_manager import firestore_database def ban_user(user_id: str): @@ -38,14 +39,6 @@ if __name__ == '__main__': listdir("/home/hapso/Desktop/Personal/servii-backend/servers/paper"), "/home/hapso/Desktop/Personal/servii-backend/servers/paper") ''' - users_ref = firebase_manager.firestore_database.collection('users') - - # Stream all documents in the users collection - users = users_ref.stream() - - # Iterate over each user document - for user in users: - # Update each document to add the 'subscription' field - user_ref = users_ref.document(user.id) - user_ref.update({'subscription': 0}) + print(firebase_manager.get_user_from_mail("technoprod25458565@gmail.com")) + print(firebase_manager.get_user_field("MpkbDMOO8PQddQgB5VgBQdTMWF53", "test")) pass