diff --git a/.fission/local-deployment.json b/.fission/local-deployment.json index 8854184..dc9e0cb 100644 --- a/.fission/local-deployment.json +++ b/.fission/local-deployment.json @@ -3,11 +3,11 @@ "secrets": { "fission-ailbl-user-phone-env": { "literals": [ - "S3_BUCKET=ailbl", - "S3_ENDPOINT_URL=http://160.30.113.113:9000", - "S3_ACCESS_KEY_ID=quyen", - "S3_SECRET_ACCESS_KEY=12345678", - "S3_PREFIX=user/avatar" + "PG_HOST=160.30.113.113", + "PG_PORT=45432", + "PG_DB=postgres", + "PG_USER=postgres", + "PG_PASS=q2q32RQx9R9qVAp3vkVrrASnSUUhzKvC" ] } } diff --git a/apps/ailbl-admin_avatar-insert-update-delete-get.py b/apps/ailbl-admin_avatar-insert-update-delete-get.py index 7f36a4a..881ef05 100644 --- a/apps/ailbl-admin_avatar-insert-update-delete-get.py +++ b/apps/ailbl-admin_avatar-insert-update-delete-get.py @@ -1,32 +1,30 @@ import crud from flask import jsonify, request -ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/png", "image/gif", "image/webp"} + def main(): """ ```fission { - "name": "avatar-admin-get-insert-delete-put", + "name": "phone-admin-get-insert-delete", "http_triggers": { - "avatar-admin-get-insert-delete-put-http": { - "url": "/ailbl/admin/avatars", - "methods": ["PUT", "POST", "DELETE", "GET"] + "phone-admin-get-insert-delete-http": { + "url": "/ailbl/admin/users/{UserId}/phones", + "methods": [ "POST", "DELETE", "GET"] } } } ``` """ try: - if request.method == "PUT": - return make_update_avatar_request() - elif request.method == "DELETE": - return make_delete_avatar_request() + if request.method == "DELETE": + return make_delete_request() elif request.method == "POST": return make_insert_request() elif request.method == "GET": - return make_get_avatar_request() + return make_get_request() else: return {"error": "Method not allow"}, 405 except Exception as ex: @@ -36,54 +34,32 @@ def main(): def make_insert_request(): try: user_id = request.headers.get("X-User") - file = request.files.get("avatar") - if not user_id or not file: - return jsonify({"error": "user_id or file is required"}), 400 - if file.mimetype not in ALLOWED_IMAGE_TYPES: - return jsonify( - {"error": "Invalid file type. Only JPG, PNG, GIF, WEBP are allowed."} - ), 400 - response, status = crud.update_or_create_avatar(user_id, file) + + response, status = crud return jsonify(response), status except Exception as e: return jsonify({"error": str(e)}), 500 -def make_get_avatar_request(): +def make_get_request(): try: user_id = request.headers.get("X-User") if not user_id: return jsonify({"error": "user_id is required"}), 400 - return crud.get_avatar_url(user_id) + return crud except Exception as e: return jsonify({"error": str(e)}), 500 -def make_delete_avatar_request(): +def make_delete_request(): try: user_id = request.headers.get("X-User") if not user_id: return jsonify({"error": "user_id is required"}), 400 - response, status = crud.delete_avatar(user_id) + response, status = crud return jsonify(response), status except Exception as e: return jsonify({"error": str(e)}), 500 - -def make_update_avatar_request(): - try: - user_id = request.headers.get("X-User") - file = request.files.get("avatar") - if not user_id or not file: - return jsonify({"error": "user_id or file is required"}), 400 - if file.mimetype not in ALLOWED_IMAGE_TYPES: - return jsonify( - {"error": "Invalid file type. Only JPG, PNG, GIF, WEBP are allowed."} - ), 400 - - response, status = crud.update_or_create_avatar(user_id, file) - return jsonify(response), status - except Exception as e: - return jsonify({"error": str(e)}), 500 diff --git a/apps/ailbl-user_avatar-insert-update-delete-get.py b/apps/ailbl-user_avatar-insert-update-delete-get.py index b851dd5..e3c65d1 100644 --- a/apps/ailbl-user_avatar-insert-update-delete-get.py +++ b/apps/ailbl-user_avatar-insert-update-delete-get.py @@ -1,33 +1,30 @@ import crud from flask import jsonify, request -# from storage.minio_client import get_minio_client, check_existing_avatar_on_minio, upload_to_minio -ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/png", "image/gif", "image/webp"} + def main(): """ ```fission { - "name": "avatar-users-get-insert-delete-put", + "name": "phone-users-get-insert-delete", "http_triggers": { - "avatar-users-get-insert-delete-put-http": { - "url": "/ailbl/users/avatars", - "methods": ["PUT", "POST", "DELETE", "GET"] + "phone-users-get-insert-delete-http": { + "url": "/ailbl/users/phones", + "methods": ["POST", "DELETE", "GET"] } } } ``` """ try: - if request.method == "PUT": - return make_update_avatar_request() - elif request.method == "DELETE": - return make_delete_avatar_request() + if request.method == "DELETE": + return make_delete_request() elif request.method == "POST": return make_insert_request() elif request.method == "GET": - return make_get_avatar_request() + return make_get_request() else: return {"error": "Method not allow"}, 405 except Exception as ex: @@ -41,55 +38,36 @@ def make_insert_request(): file = request.files.get("avatar") if not user_id or not file: return jsonify({"error": "user_id or file is required"}), 400 - # Check mimetype(kieu du lieu cua file anh) - if file.mimetype not in ALLOWED_IMAGE_TYPES: - return jsonify( - {"error": "Invalid file type. Only JPG, PNG, GIF, WEBP are allowed."} - ), 400 - response, status = crud.update_or_create_avatar(user_id, file) + + + response, status = crud return jsonify(response), status except Exception as e: return jsonify({"error": str(e)}), 500 -def make_update_avatar_request(): - try: - # Lay user_id tu header X-User, neu co giao dien roi thi cookies se tu dong duoc gui len o trong header - user_id = request.headers.get("X-User") - # Lay file tu form-data voi key la 'avatar' - file = request.files.get("avatar") - if not user_id or not file: - return jsonify({"error": "user_id or file is required"}), 400 - # Check mimetype(kieu du lieu cua file anh) - if file.mimetype not in ALLOWED_IMAGE_TYPES: - return jsonify( - {"error": "Invalid file type. Only JPG, PNG, GIF, WEBP are allowed."} - ), 400 - response, status = crud.update_or_create_avatar( - user_id, file) # Call CRUD function to update avatar - return jsonify(response), status - except Exception as e: - return jsonify({"error": str(e)}), 500 -def make_delete_avatar_request(): +def make_delete_request(): try: user_id = request.headers.get("X-User") # Lay user_id tu header X-User if not user_id: return jsonify({"error": "user_id is required"}), 400 # Call CRUD function to delete avatar - response, status = crud.delete_avatar(user_id) + + response, status = crud return jsonify(response), status except Exception as e: return jsonify({"error": str(e)}), 500 -def make_get_avatar_request(): +def make_get_request(): try: user_id = request.headers.get("X-User") if not user_id: return jsonify({"error": "user_id is required"}), 400 - return crud.get_avatar_url(user_id) + + return crud # return jsonify(response), status except Exception as e: return jsonify({"error": str(e)}), 500 diff --git a/apps/crud.py b/apps/crud.py index 225daae..5c6ce0b 100644 --- a/apps/crud.py +++ b/apps/crud.py @@ -6,18 +6,9 @@ from PIL import Image # Create&Update function to upload or update user avatar S3/Minio -def update_or_create_avatar(user_id: str, file): +def create_phone(user_id: str, file): try: - file_data = file.read() - # Bản chất là đường dẫn trong bucket + tên file = user_id - object_name = f"{get_secret('S3_PREFIX')}/{user_id}" - result = s3_client.put_object( - Bucket=S3_BUCKET, - Key=object_name, - Body=io.BytesIO(file_data), - ContentLength=len(file_data), - ContentType=file.content_type, - ) + return result, 200 @@ -25,28 +16,12 @@ def update_or_create_avatar(user_id: str, file): return {"error": str(e)}, 500 -def get_avatar_url(user_id: str): # Read function to get user avatar from S3/Minio +def get_phone(user_id: str): # Read function to get user avatar from S3/Minio try: - response = s3_client.get_object( - Bucket=S3_BUCKET, - Key=f"{get_secret('S3_PREFIX')}/{user_id}" - ) - # image_data = response["body"].read(content_type) - image_data = response['Body'].read() - - with Image.open(io.BytesIO(image_data)) as img: - fmt = img.format.lower() # ví dụ: 'jpeg', 'png', 'webp' - content_type = f"image/{'jpeg' if fmt == 'jpg' else fmt}" - # return Response( - # io.BytesIO(image_data), - # content_type=content_type, - # direct_passthrough=True, - # ) + return Response( - image_data, - content_type=content_type, - direct_passthrough=True + ), 200 except Exception as e: @@ -54,12 +29,10 @@ def get_avatar_url(user_id: str): # Read function to get user avatar from S3/Mi # Delete Function to delete user avatar from S3/Minio -def delete_avatar(user_id: str) -> dict: +def delete_phone(user_id: str) -> dict: try: - result = s3_client.delete_object( - Bucket=S3_BUCKET, - Key=f"{get_secret('S3_PREFIX')}/{user_id}" - ) + + return result, 200 except Exception as e: return {"error": str(e)}, 500 diff --git a/apps/helpers.py b/apps/helpers.py index 27594f9..5d8ccae 100644 --- a/apps/helpers.py +++ b/apps/helpers.py @@ -1,35 +1,96 @@ +import datetime import logging -import boto3 -SECRET_NAME = "fission-ailbl-user-avatar-env" -K8S_NAMESPACE = "default" +import socket +import typing +import psycopg2 +from flask import current_app +from psycopg2.extras import LoggingConnection + + +CORS_HEADERS = { + "Content-Type": "application/json", +} + + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger(__name__) + + +def init_db_connection(): + db_host = get_secret("PG_HOST", "locahost") + db_port = int(get_secret("PG_PORT", 55432)) + + if not check_port_open(ip=db_host, port=db_port): + raise Exception( + f"Establishing A Database Connection. {db_host}:{db_port}") + + # options = get_secret("PG_DBSCHEMA") + # if options: + # options = f"-c search_path={options}" # if specific db schema + conn = psycopg2.connect( + database=get_secret("PG_DB", "postgres"), + user=get_secret("PG_USER", "postgres"), + password=get_secret("PG_PASS", "secret"), + host=get_secret("PG_HOST", "127.0.0.1"), + port=int(get_secret("PG_PORT", 5432)), + # options=options, + # cursor_factory=NamedTupleCursor, + connection_factory=LoggingConnection, + ) + conn.initialize(logger) + return conn + + +# def db_row_to_dict(cursor, row): +# record = {} +# for i, column in enumerate(cursor.description): +# data = row[i] +# if isinstance(data, datetime.datetime): +# data = data.isoformat() +# record[column.name] = data +# return record + +def db_row_to_dict(cursor, row): + record = {} + for i, column in enumerate(cursor.description): + data = row[i] + if isinstance(data, (datetime.datetime, datetime.date)): + data = data.isoformat() + record[column.name] = data + return record + + +def db_rows_to_array(cursor, rows): + return [db_row_to_dict(cursor, row) for row in rows] def get_current_namespace() -> str: try: with open("/var/run/secrets/kubernetes.io/serviceaccount/namespace", "r") as f: namespace = f.read() - except: + except Exception as err: + current_app.logger.error(err) namespace = K8S_NAMESPACE return str(namespace) -def get_secret(key: str, default=None) -> str: +def get_secret(key: str, default=None): namespace = get_current_namespace() path = f"/secrets/{namespace}/{SECRET_NAME}/{key}" try: with open(path, "r") as f: return f.read() - except: + except Exception as err: + current_app.logger.error(path, err) return default -S3_BUCKET = get_secret("S3_BUCKET") -S3_PREFIX = get_secret("S3_PREFIX") - -s3_client = boto3.client( - "s3", - endpoint_url=get_secret("S3_ENDPOINT_URL"), - aws_access_key_id=get_secret("S3_ACCESS_KEY_ID"), - aws_secret_access_key=get_secret("S3_SECRET_ACCESS_KEY"), - config=boto3.session.Config(signature_version="s3v4"), -) +def check_port_open(ip: str, port: int, timeout: int = 30): + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.settimeout(timeout) + result = s.connect_ex((ip, port)) + return result == 0 + except Exception as err: + current_app.logger.err(f"Check port open error: {err}") + return False