from flask import current_app, jsonify, request from helpers import CORS_HEADERS, db_row_to_dict, init_db_connection from psycopg2 import IntegrityError from pydantic_core import ValidationError from schemas import AiUserUpdate def main(): """ ```fission { "name": "ai-admin-update-delete-user", "fntimeout": 300, "http_triggers": { "ai-admin-update-delete-user-http": { "url": "/ailbl/ai/admin/users/{UserID}", "methods": ["DELETE", "PUT"] } } } ``` """ try: if request.method == "DELETE": return make_delete_request() elif request.method == "PUT": return make_update_request() else: return {"error": "Method not allow"}, 405, CORS_HEADERS except Exception as err: print(f"ErrorType={type(err)}") return {"error": str(err)}, 500, CORS_HEADERS def make_update_request(): r"""make_update_request() -> tuple[Response, int, dict] Update an existing user by ID. Retrieves the user ID from ``X-Fission-Params-UserID`` header, validates the request body using :class:`AiUserUpdate` schema, and performs a partial update on the user record. Uses row-level locking (``SELECT ... FOR UPDATE``) to prevent concurrent modification conflicts. Returns: tuple: A tuple containing: - JSON response with updated user data or error details - HTTP status code (200 on success, 400/404/409 on error) - CORS headers dict Raises: ValidationError: If request body fails Pydantic validation (returns 400). IntegrityError: If email conflicts with another user (returns 409). Example:: >>> # PUT /ai/admin/users/550e8400-e29b-41d4-a716-446655440000 >>> # Header: X-Fission-Params-UserID: 550e8400-e29b-41d4-a716-446655440000 >>> # Body: {"name": "Jane Doe"} >>> # Response: 200 OK >>> { ... "id": "550e8400-e29b-41d4-a716-446655440000", ... "name": "Jane Doe", ... "email": "john@example.com", ... "modified": "2024-01-02T10:00:00" ... } """ user_id = request.headers.get("X-Fission-Params-UserID") if not user_id: return jsonify({"errorCode": "MISSING_USER_ID"}), 400, CORS_HEADERS try: body = AiUserUpdate(**(request.get_json(silent=True) or {})) except ValidationError as e: return ( jsonify({"error": "Validation failed", "details": e.errors()}), 400, CORS_HEADERS, ) conn = None try: conn = init_db_connection() with conn: with conn.cursor() as cur: cur.execute( "SELECT * FROM ai_user WHERE id=%s FOR UPDATE", (user_id,)) row = cur.fetchone() if not row: return jsonify({"errorCode": "USER_NOT_FOUND"}), 404, CORS_HEADERS sets, params = [], {"id": user_id} if body.name is not None: sets.append("name=%(name)s") params["name"] = body.name if body.email is not None: sets.append("email=%(email)s") params["email"] = body.email if body.dob is not None: sets.append("dob=%(dob)s") params["dob"] = body.dob if body.gender is not None: sets.append("gender=%(gender)s") params["gender"] = body.gender sets.append("modified=CURRENT_TIMESTAMP") cur.execute( f"UPDATE ai_user SET {', '.join(sets)} WHERE id=%(id)s RETURNING *", params, ) updated = db_row_to_dict(cur, cur.fetchone()) return jsonify(updated), 200, CORS_HEADERS except IntegrityError as e: return ( jsonify({"errorCode": "DUPLICATE_USER", "details": str(e)}), 409, CORS_HEADERS, ) finally: if conn: conn.close() def __delete_user(cursor, id: str): r"""Delete a user from the database by ID. Args: cursor: Database cursor object for executing queries. id (str): UUID of the user to delete. Returns: dict | str: User data dict if deleted successfully, or ``"USER_NOT_FOUND"`` string if user doesn't exist. Note: This is a private function. Use :func:`make_delete_request` instead. """ cursor.execute("SELECT 1 FROM ai_user WHERE id = %(id)s", {"id": id}) if not cursor.fetchone(): return "USER_NOT_FOUND" cursor.execute("DELETE FROM ai_user WHERE id = %(id)s RETURNING *", {"id": id}) row = cursor.fetchone() return db_row_to_dict(cursor, row) def make_delete_request(): r"""make_delete_request() -> tuple[Response, int, dict] Delete a user by ID. Retrieves the user ID from ``X-Fission-Params-UserID`` header and deletes the user from the database if found. Returns: tuple: A tuple containing: - JSON response with deleted user data or error details - HTTP status code (200 on success, 400/404/500 on error) - CORS headers dict (may be omitted on some error responses) Example:: >>> # DELETE /ai/admin/users/550e8400-e29b-41d4-a716-446655440000 >>> # Header: X-Fission-Params-UserID: 550e8400-e29b-41d4-a716-446655440000 >>> # Response: 200 OK >>> { ... "id": "550e8400-e29b-41d4-a716-446655440000", ... "name": "John Doe", ... "email": "john@example.com" ... } """ user_id = request.headers.get("X-Fission-Params-UserID") if not user_id: return jsonify({"errorCode": "MISSING_USER_ID"}), 400, CORS_HEADERS conn = None try: conn = init_db_connection() with conn.cursor() as cursor: result = __delete_user(cursor, id=user_id) if result == "USER_NOT_FOUND": return jsonify({"errorCode": "USER_NOT_FOUND"}), 404 conn.commit() return jsonify(result), 200 except Exception as ex: return jsonify({"error": str(ex)}), 500 finally: if conn is not None: conn.close() current_app.logger.info("Close DB connection")