2024-09-09 23:05:25 +00:00
|
|
|
import atexit
|
2024-06-21 18:44:33 +00:00
|
|
|
import http
|
2024-06-24 00:50:08 +00:00
|
|
|
import inspect
|
2024-09-09 23:05:25 +00:00
|
|
|
import os
|
|
|
|
from mailbox import FormatError
|
2024-07-14 17:23:59 +00:00
|
|
|
from typing import Union
|
2024-06-21 18:44:33 +00:00
|
|
|
|
2024-09-09 23:05:25 +00:00
|
|
|
from apscheduler.schedulers.background import BackgroundScheduler
|
|
|
|
from firebase_admin.auth import UserNotFoundError, UserRecord
|
2024-07-01 13:42:28 +00:00
|
|
|
from flask import (Blueprint, Flask, Response, jsonify, request)
|
2024-06-14 21:33:55 +00:00
|
|
|
from flask_cors import CORS
|
2024-09-09 23:05:25 +00:00
|
|
|
from werkzeug.datastructures import ImmutableMultiDict, FileStorage
|
|
|
|
from werkzeug.utils import secure_filename
|
2024-06-21 18:44:33 +00:00
|
|
|
|
2024-09-09 23:05:25 +00:00
|
|
|
import file_manager
|
2024-06-24 14:42:41 +00:00
|
|
|
import firebase_manager
|
2024-06-14 21:33:55 +00:00
|
|
|
import generic_executor
|
|
|
|
|
|
|
|
app = Flask(__name__)
|
2024-09-09 23:05:25 +00:00
|
|
|
app.config['MAX_CONTENT_LENGTH'] = 16 * 1000 * 1000 * 1000
|
2024-06-29 20:40:26 +00:00
|
|
|
cors = CORS(app, origins="*")
|
|
|
|
apiBP = Blueprint('apiBP', 'BPapi')
|
2024-06-14 21:33:55 +00:00
|
|
|
|
2024-06-20 16:57:04 +00:00
|
|
|
|
2024-06-24 00:50:08 +00:00
|
|
|
def generic_response_maker(status_code: http.HTTPStatus, _message: str = None) -> tuple[Response, int]:
|
|
|
|
if _message is not None:
|
|
|
|
return jsonify({'message': _message}), status_code.value
|
2024-06-21 18:44:33 +00:00
|
|
|
match status_code:
|
|
|
|
case http.HTTPStatus.CREATED:
|
|
|
|
message = jsonify({'message': 'Creation successful.'})
|
|
|
|
case http.HTTPStatus.INTERNAL_SERVER_ERROR:
|
|
|
|
message = jsonify({'message': 'Internal Server Error.'})
|
|
|
|
case http.HTTPStatus.NO_CONTENT:
|
2024-06-25 04:55:49 +00:00
|
|
|
message = jsonify({'message': 'Deletion successful'})
|
2024-06-21 18:44:33 +00:00
|
|
|
case http.HTTPStatus.ACCEPTED:
|
|
|
|
message = jsonify({'message': 'Action successful.'})
|
|
|
|
case http.HTTPStatus.BAD_REQUEST:
|
2024-09-09 23:05:25 +00:00
|
|
|
message = jsonify({'message': 'Bad Request.'})
|
2024-06-21 18:44:33 +00:00
|
|
|
case http.HTTPStatus.NOT_FOUND:
|
|
|
|
message = jsonify({'message': 'Server not found.'})
|
|
|
|
case http.HTTPStatus.UNSUPPORTED_MEDIA_TYPE:
|
|
|
|
message = jsonify({'message': 'Unsupported Media Type / No JSON payload'})
|
|
|
|
case http.HTTPStatus.OK:
|
|
|
|
message = jsonify({'message': 'Success.'})
|
2024-06-24 00:50:08 +00:00
|
|
|
case http.HTTPStatus.METHOD_NOT_ALLOWED:
|
|
|
|
message = jsonify({'message': 'This API call does not exist.'})
|
2024-06-21 18:44:33 +00:00
|
|
|
case _:
|
2024-06-25 04:55:49 +00:00
|
|
|
message = jsonify({'message': 'Could not process request.'})
|
2024-06-21 18:44:33 +00:00
|
|
|
return message, status_code.value
|
|
|
|
|
|
|
|
|
2024-06-29 16:15:34 +00:00
|
|
|
'''
|
|
|
|
valid, user_id = firebase_manager.verify_jwt_token(data['token'])
|
2024-07-08 20:05:07 +00:00
|
|
|
TODO : replace 53 by the given statement.
|
2024-06-29 16:15:34 +00:00
|
|
|
'''
|
|
|
|
|
|
|
|
|
2024-06-25 04:55:49 +00:00
|
|
|
def authenticate_request(data: dict):
|
|
|
|
if 'token' not in data:
|
2024-06-24 14:42:41 +00:00
|
|
|
raise Exception("Missing 'token' in request body. The API doesn't support anonymous access anymore.")
|
|
|
|
else:
|
2024-06-25 04:55:49 +00:00
|
|
|
valid, user_id = True, data['token']
|
2024-06-24 14:42:41 +00:00
|
|
|
if not valid:
|
|
|
|
raise Exception("Invalid JWT token.")
|
|
|
|
else:
|
|
|
|
user = firebase_manager.get_user_from_id(user_id)
|
|
|
|
if not user:
|
|
|
|
raise Exception("User not found.")
|
|
|
|
if not user.email_verified:
|
|
|
|
raise Exception("Your google account isn't verified yet.")
|
2024-06-25 04:55:49 +00:00
|
|
|
return user
|
|
|
|
|
|
|
|
|
2024-07-14 17:23:59 +00:00
|
|
|
def parse_and_validate_request(parameters: list[str]) -> Union[list[str], None]:
|
2024-06-25 04:55:49 +00:00
|
|
|
args = []
|
|
|
|
data = request.get_json()
|
|
|
|
if not data:
|
|
|
|
raise Exception("Empty request body.")
|
|
|
|
user = authenticate_request(data)
|
|
|
|
data['user'] = user
|
2024-06-24 00:50:08 +00:00
|
|
|
for name in parameters:
|
|
|
|
if name not in data:
|
|
|
|
raise Exception(f"Missing parameter {name}")
|
|
|
|
value = data[name]
|
2024-06-25 04:55:49 +00:00
|
|
|
args.append(value)
|
2024-06-24 00:50:08 +00:00
|
|
|
return args
|
|
|
|
|
|
|
|
|
|
|
|
route_handlers = {
|
2024-06-25 04:55:49 +00:00
|
|
|
'SetSubdomain': generic_executor.set_subdomain,
|
|
|
|
'FetchServers': generic_executor.fetch_servers,
|
2024-08-15 10:07:45 +00:00
|
|
|
'FetchLogs': generic_executor.fetch_logs,
|
2024-08-20 16:56:43 +00:00
|
|
|
'FetchHistory': generic_executor.fetch_history,
|
2024-08-24 11:23:19 +00:00
|
|
|
'FetchPlayersStatus': generic_executor.fetch_players_status,
|
2024-06-24 00:50:08 +00:00
|
|
|
'AccountCreate': generic_executor.account_create,
|
|
|
|
'ServerCreate': generic_executor.server_create,
|
|
|
|
'ServerDelete': generic_executor.server_delete,
|
|
|
|
'AccountDelete': generic_executor.account_delete,
|
|
|
|
'ServerRun': generic_executor.server_run,
|
|
|
|
'ServerStop': generic_executor.server_stop,
|
2024-07-08 20:05:07 +00:00
|
|
|
'UpdateProperties': generic_executor.update_properties,
|
2024-06-24 00:50:08 +00:00
|
|
|
'Command': generic_executor.run_command,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-06-29 16:31:47 +00:00
|
|
|
@apiBP.route('/<path:path>', methods=['POST'])
|
2024-06-24 00:50:08 +00:00
|
|
|
def dynamic_route_handler(path):
|
|
|
|
if path not in route_handlers:
|
|
|
|
return generic_response_maker(http.HTTPStatus.METHOD_NOT_ALLOWED)
|
|
|
|
|
|
|
|
route_fn = route_handlers[path]
|
|
|
|
parameters = []
|
|
|
|
sig = inspect.signature(route_fn)
|
|
|
|
for param in sig.parameters.values():
|
|
|
|
parameters.append(param.name)
|
2024-06-29 16:15:34 +00:00
|
|
|
try:
|
2024-06-24 14:42:41 +00:00
|
|
|
mapped_parameters = parse_and_validate_request(parameters)
|
2024-06-25 04:55:49 +00:00
|
|
|
if mapped_parameters is None:
|
|
|
|
return generic_response_maker(http.HTTPStatus.BAD_REQUEST)
|
|
|
|
status, message = route_fn(*mapped_parameters)
|
|
|
|
if isinstance(message, list):
|
|
|
|
return jsonify(message), http.HTTPStatus.OK
|
|
|
|
return generic_response_maker(status, message if message else None)
|
2024-06-24 14:42:41 +00:00
|
|
|
except Exception as e:
|
|
|
|
return generic_response_maker(http.HTTPStatus.BAD_REQUEST, str(e))
|
2024-06-14 21:33:55 +00:00
|
|
|
|
|
|
|
|
2024-09-09 23:05:25 +00:00
|
|
|
# [!] This route is specific and has to remain out of the dynamic route handler.
|
|
|
|
@apiBP.route('/Upload', methods=['POST'])
|
|
|
|
def upload():
|
|
|
|
form = request.form
|
|
|
|
token: str or None = form.get('token')
|
|
|
|
name: str or None = form.get('name')
|
|
|
|
files: ImmutableMultiDict[str, FileStorage] = request.files
|
|
|
|
|
|
|
|
try:
|
|
|
|
if not form:
|
|
|
|
raise FormatError(0)
|
|
|
|
if not files:
|
|
|
|
raise FormatError(1)
|
|
|
|
if not token:
|
|
|
|
raise KeyError('token')
|
|
|
|
if not name:
|
|
|
|
raise KeyError('name')
|
|
|
|
|
|
|
|
data: dict = {'token': token}
|
|
|
|
user: UserRecord = authenticate_request(data)
|
|
|
|
user_id: str = user.uid
|
|
|
|
|
|
|
|
for _, file in files.items():
|
|
|
|
filename = file.filename
|
|
|
|
internal_path = file_manager.get_path_from_extension(filename)
|
|
|
|
if internal_path is None:
|
|
|
|
continue
|
|
|
|
filename = secure_filename(filename)
|
|
|
|
file.save(os.path.join(f"users/{user_id}/{name}/{internal_path}", filename))
|
|
|
|
|
|
|
|
except FormatError as e:
|
|
|
|
match e:
|
|
|
|
case 0:
|
|
|
|
return generic_response_maker(http.HTTPStatus.BAD_REQUEST, "No FormData found in the payload.")
|
|
|
|
case 1:
|
|
|
|
return generic_response_maker(http.HTTPStatus.NOT_ACCEPTABLE, "No file(s) were uploaded.")
|
|
|
|
|
|
|
|
except FileNotFoundError as e:
|
|
|
|
return generic_response_maker(http.HTTPStatus.CONFLICT, "Please launch the server at least once.")
|
|
|
|
|
|
|
|
except KeyError as e:
|
|
|
|
return f"Missing parameter {e} in FormData.", http.HTTPStatus.BAD_REQUEST
|
|
|
|
|
|
|
|
except UserNotFoundError as e:
|
|
|
|
return str(e), http.HTTPStatus.BAD_REQUEST
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
file_manager.log_error(type(e).__name__, str(e))
|
|
|
|
return generic_response_maker(http.HTTPStatus.BAD_REQUEST, f"{type(e).__name__}, {str(e)}")
|
|
|
|
|
|
|
|
return generic_response_maker(http.HTTPStatus.OK, "Successfully uploaded files !")
|
|
|
|
|
|
|
|
|
2024-08-24 11:35:59 +00:00
|
|
|
def exit_safety() -> None:
|
|
|
|
firebase_manager.set_servers_not_running()
|
|
|
|
return
|
|
|
|
|
2024-06-29 20:40:26 +00:00
|
|
|
app.register_blueprint(apiBP)
|
|
|
|
|
2024-06-14 21:33:55 +00:00
|
|
|
if __name__ == '__main__':
|
2024-08-24 11:35:59 +00:00
|
|
|
atexit.register(exit_safety)
|
2024-09-06 10:43:28 +00:00
|
|
|
scheduler = BackgroundScheduler()
|
2024-09-06 10:51:13 +00:00
|
|
|
scheduler.add_job(generic_executor.scheduled_actions, 'interval', minutes=10)
|
2024-09-06 10:43:28 +00:00
|
|
|
scheduler.start()
|
2024-06-29 20:40:26 +00:00
|
|
|
app.run(host='0.0.0.0', port=3000, debug=False)
|