From a246e3d77804b1aa649cba85bdc91c6ca8da516a Mon Sep 17 00:00:00 2001 From: QuangMinh_123 Date: Mon, 8 Dec 2025 07:50:09 +0000 Subject: [PATCH] CRUD_UserProfile --- .fission/local-deployment.json | 12 +- ...l-admin_avatar-insert-update-delete-get.py | 89 ----------- ...-admin_profile-insert-get-update-delete.py | 68 +++++++++ ...bl-user_avatar-insert-update-delete-get.py | 95 ------------ apps/ailbl-users_profile-insert-get-update.py | 73 +++++++++ apps/crud.py | 140 ++++++++++++------ apps/helpers.py | 97 ++++++++++-- 7 files changed, 323 insertions(+), 251 deletions(-) delete mode 100644 apps/ailbl-admin_avatar-insert-update-delete-get.py create mode 100644 apps/ailbl-admin_profile-insert-get-update-delete.py delete mode 100644 apps/ailbl-user_avatar-insert-update-delete-get.py create mode 100644 apps/ailbl-users_profile-insert-get-update.py diff --git a/.fission/local-deployment.json b/.fission/local-deployment.json index d84e6cc..662afee 100644 --- a/.fission/local-deployment.json +++ b/.fission/local-deployment.json @@ -3,11 +3,13 @@ "secrets": { "fission-ailbl-user-profile-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", + "KRATOS_ADMIN_ENDPOINT=http://160.30.113.113:4434", + "KRATOS_PUBLIC_ENDPOINT=http://160.30.113.113:4433" ] } } diff --git a/apps/ailbl-admin_avatar-insert-update-delete-get.py b/apps/ailbl-admin_avatar-insert-update-delete-get.py deleted file mode 100644 index 7f36a4a..0000000 --- a/apps/ailbl-admin_avatar-insert-update-delete-get.py +++ /dev/null @@ -1,89 +0,0 @@ -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", - "http_triggers": { - "avatar-admin-get-insert-delete-put-http": { - "url": "/ailbl/admin/avatars", - "methods": ["PUT", "POST", "DELETE", "GET"] - } - } - } - ``` - """ - try: - if request.method == "PUT": - return make_update_avatar_request() - elif request.method == "DELETE": - return make_delete_avatar_request() - elif request.method == "POST": - return make_insert_request() - elif request.method == "GET": - return make_get_avatar_request() - else: - return {"error": "Method not allow"}, 405 - except Exception as ex: - return jsonify({"error": str(ex)}), 500 - - -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) - return jsonify(response), status - except Exception as e: - return jsonify({"error": str(e)}), 500 - - -def make_get_avatar_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) - except Exception as e: - return jsonify({"error": str(e)}), 500 - - -def make_delete_avatar_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) - 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-admin_profile-insert-get-update-delete.py b/apps/ailbl-admin_profile-insert-get-update-delete.py new file mode 100644 index 0000000..b8786b0 --- /dev/null +++ b/apps/ailbl-admin_profile-insert-get-update-delete.py @@ -0,0 +1,68 @@ +import crud +from flask import jsonify, request + + + +def main(): + """ + ```fission + { + "name": "profile-admin-get-insert-delete-put", + "http_triggers": { + "profile-admin-get-insert-delete-put-http": { + "url": "/ailbl/admin/users/{user_id}/profiles", + "methods": ["POST", "PUT", "DELETE", "GET"] + } + } + } + ``` + """ + try: + if request.method == "PUT": + return make_update_request() + elif request.method == "DELETE": + return make_delete_request() + elif request.method == "POST": + return make_insert_request() + elif request.method == "GET": + return make_get_request() + else: + return {"error": "Method not allow"}, 405 + except Exception as ex: + return jsonify({"error": str(ex)}), 500 + + +def make_insert_request(): + try: + + response, status = crud + return jsonify(response), status + except Exception as e: + return jsonify({"error": str(e)}), 500 + + +def make_update_request(): + try: + + response, status = crud# Call CRUD function to update avatar + return jsonify(response), status + except Exception as e: + return jsonify({"error": str(e)}), 500 + + +def make_delete_request(): + try: + + response, status = crud + return jsonify(response), status + except Exception as e: + return jsonify({"error": str(e)}), 500 + + +def make_get_request(): + try: + + return crud + # 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 deleted file mode 100644 index b851dd5..0000000 --- a/apps/ailbl-user_avatar-insert-update-delete-get.py +++ /dev/null @@ -1,95 +0,0 @@ -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", - "http_triggers": { - "avatar-users-get-insert-delete-put-http": { - "url": "/ailbl/users/avatars", - "methods": ["PUT", "POST", "DELETE", "GET"] - } - } - } - ``` - """ - try: - if request.method == "PUT": - return make_update_avatar_request() - elif request.method == "DELETE": - return make_delete_avatar_request() - elif request.method == "POST": - return make_insert_request() - elif request.method == "GET": - return make_get_avatar_request() - else: - return {"error": "Method not allow"}, 405 - except Exception as ex: - return jsonify({"error": str(ex)}), 500 - - -def make_insert_request(): - try: - user_id = request.headers.get("X-User") # Lay user_id tu header 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) - 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(): - 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) - return jsonify(response), status - except Exception as e: - return jsonify({"error": str(e)}), 500 - - -def make_get_avatar_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 jsonify(response), status - except Exception as e: - return jsonify({"error": str(e)}), 500 diff --git a/apps/ailbl-users_profile-insert-get-update.py b/apps/ailbl-users_profile-insert-get-update.py new file mode 100644 index 0000000..a0ca532 --- /dev/null +++ b/apps/ailbl-users_profile-insert-get-update.py @@ -0,0 +1,73 @@ +import crud +from flask import jsonify, request +from helpers import init_db_connection + + + +def main(): + """ + ```fission + { + "name": "profile-users-get-insert-put", + "http_triggers": { + "profile-users-get-insert-put-http": { + "url": "/ailbl/users/profiles", + "methods": ["POST", "PUT", "GET"] + } + } + } + ``` + """ + try: + if request.method == "PUT": + return make_update_request() + elif request.method == "POST": + return make_insert_request() + elif request.method == "GET": + return make_get_request() + else: + return {"error": "Method not allow"}, 405 + except Exception as ex: + return jsonify({"error": str(ex)}), 500 + + +def make_insert_request(): + try: + data = request.get_json() # Lay du lieu json tu request body + if not data.get("user_id"): + return jsonify({"error": "user_id is required"}), 400 + + response, status = crud.insert_profile(data) + + return jsonify(response), status + except Exception as e: + return jsonify({"error": str(e)}), 500 + + +def make_update_request(): + try: + data = request.get_json() # Lay du lieu json tu request body + user_id = data.get("user_id") + + if not user_id: + return jsonify({"error": "user_id is required"}), 400 + + # Call CRUD function to update profile + response, status = crud.update_profile + return jsonify(response), status + + except Exception as e: + return jsonify({"error": str(e)}), 500 + + +def make_get_request(): + try: + user_id = request.headers.get("X-User") + if not user_id: + return jsonify({"error": "user_id is required"}), 400 + + response, status = crud.get_profile(user_id) + 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..a33a3b8 100644 --- a/apps/crud.py +++ b/apps/crud.py @@ -1,65 +1,109 @@ import io - from flask import Response -from helpers import S3_BUCKET, get_secret, s3_client +from helpers import init_db_connection, CORS_HEADERS 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 get_profile(user_id): 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, - ) + conn = init_db_connection() + cursor = conn.cursor() - return result, 200 + # Truy van thong tin nguoi dung tu bang ailbl_user_profiles + query = "SELECT * FROM ailbl_user_profiles WHERE user_id = %s" + cursor.execute(query, (user_id,)) + profile = cursor.fetchone() # fetchone la gi ? + + if profile: + return { + "user_id": profile[0], + "first_name": profile[1], + "last_name": profile[2], + "dob": profile[3], + "gender": profile[4], + "address": profile[5], + "phone": profile[6], + "created": profile[7], + "modified": profile[8] + }, 200, CORS_HEADERS + else: + return {"error": "Profile not found"}, 404, CORS_HEADERS + except Exception as e: + return {"error": str(e)}, 500, CORS_HEADERS + finally: + if conn: + conn.close() + + +def update_profile(user_id, data): + try: + conn = init_db_connection() + cursor = conn.cursor() + # Cap nhat thong tin nguoi dung trong bang ailbl_user_profiles + query = """ + UPDATE ailbl_user_profiles + SET first_name = %s, last_name = %s, dob = %s, gender = %s, address = %s, phone = %s, modified = NOW() + WHERE user_id = %s + """ + cursor.execute(query, ( + data.get("first_name"), + data.get("last_name"), + data.get("dob"), + data.get("gender"), + data.get("address"), + data.get("phone"), + user_id + )) + conn.commit() + return {"message": "Profile updated successfully"}, 200, CORS_HEADERS except Exception as e: - return {"error": str(e)}, 500 + return {"error": str(e)}, 500, CORS_HEADERS + finally: + if conn: + conn.close() -def get_avatar_url(user_id: str): # Read function to get user avatar from S3/Minio +def insert_profile(user_id, data): 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 - + conn = init_db_connection() + cursor = conn.cursor() + # Tao moi thong tin nguoi dung trong bang ailbl_user_profiles + query = """ + INSERT INTO ailbl_user_profiles (user_id, first_name, last_name, dob, gender, address, phone, created, modified) + VALUES (%s, %s, %s, %s, %s, %s, %s, NOW(), NOW()) + """ + cursor.execute(query, ( + user_id, + data.get("first_name"), + data.get("last_name"), + data.get("dob"), + data.get("gender"), + data.get("address"), + data.get("phone") + )) + conn.commit() + return {"message": "Profile created successfully"}, 201, CORS_HEADERS except Exception as e: - return {"error": str(e)}, 500 + return {"error": str(e)}, 500, CORS_HEADERS + finally: + if conn: + conn.close() -# Delete Function to delete user avatar from S3/Minio -def delete_avatar(user_id: str) -> dict: +def delete_profile(user_id): try: - result = s3_client.delete_object( - Bucket=S3_BUCKET, - Key=f"{get_secret('S3_PREFIX')}/{user_id}" - ) - return result, 200 + conn = init_db_connection() + cursor = conn.cursor() + + # Xoa thong tin nguoi dung trong bang ailbl_user_profiles + query = "DELETE FROM ailbl_user_profiles WHERE user_id = %s" + cursor.execute(query, (user_id,)) + + conn.commit() + return {"message": "Profile deleted successfully"}, 200, CORS_HEADERS except Exception as e: - return {"error": str(e)}, 500 + return {"error": str(e)}, 500, CORS_HEADERS + finally: + if conn: + conn.close() diff --git a/apps/helpers.py b/apps/helpers.py index 27594f9..6b5c990 100644 --- a/apps/helpers.py +++ b/apps/helpers.py @@ -1,35 +1,104 @@ +import datetime import logging -import boto3 -SECRET_NAME = "fission-ailbl-user-avatar-env" +import socket +import typing +import psycopg2 +from flask import current_app +from psycopg2.extras import LoggingConnection +from ory_kratos_client.api import identity_api +from ory_kratos_client.configuration import Configuration + +CORS_HEADERS = { + "Content-Type": "application/json", +} +SECRET_NAME = "fission-ailbl-user-profile-env" +CONFIG_NAME = "fission-eom-notification-config" K8S_NAMESPACE = "default" +KRATOS_ADMIN_ENDPOINT_CONFIG_KEY = "KRATOS_ADMIN_ENDPOINT" + +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") +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 + -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"), -)