From 9ccda55b4aeb81d7c71efbc416cf2fd26538919f Mon Sep 17 00:00:00 2001 From: shivang-16 Date: Tue, 25 Mar 2025 09:58:26 +0530 Subject: [PATCH 1/3] resume update api --- controllers/user.py | 69 +++++++++++-------- .../migrations/20250325042059_/migration.sql | 12 ++++ prisma/schema.prisma | 4 +- 3 files changed, 55 insertions(+), 30 deletions(-) create mode 100644 prisma/migrations/20250325042059_/migration.sql diff --git a/controllers/user.py b/controllers/user.py index 145391b..42c37da 100644 --- a/controllers/user.py +++ b/controllers/user.py @@ -388,41 +388,54 @@ async def upload_resume(): async def save_resume_data(): try: await db.connect() - - # Get the current user from the request context currentUser = g.user - - # Get the resume data from the request body resume_data = request.get_json() - + if not resume_data: - return jsonify({"success": False, "error": "No resume data provided"}), 400 + return jsonify({"error": "No resume data provided"}), 400 + + created_sections = [] + for section_type, section_items in resume_data.items(): + # Ensure proper list structure for JSON array + if not isinstance(section_items, list): + section_items = [section_items] + + prisma_content = [item if isinstance(item, dict) else {"value": item} + for item in section_items] - # Convert the resume data to a JSON string - resume_json_str = json.dumps(resume_data) - - # Prepare the data for updating the user - update_data = { - "resume": resume_json_str # Store the resume as a JSON string - } - - # Update the user with the resume data - updated_user = await db.user.update( - where={"id": currentUser.id}, - data=update_data - ) - - # Return success response + existing_section = await db.resumesection.find_first( + where={ + "userId": currentUser.id, + "sectionType": section_type + } + ) + + if existing_section: + resume_section = await db.resumesection.update( + where={"id": existing_section.id}, + data={ + "content": prisma_content, + "userId": currentUser.id # Direct scalar assignment + } + ) + else: + resume_section = await db.resumesection.create( + data={ + "sectionType": section_type, + "content": prisma_content, + "userId": currentUser.id # Required scalar field + } + ) + created_sections.append(resume_section) + return jsonify({ - "success": True, - "message": "Resume data saved successfully", - "user": updated_user.model_dump() + "success": True, + "sections": [section.model_dump() for section in created_sections] }), 200 - + except Exception as e: - print(f"Error saving resume data: {str(e)}") - return jsonify({"success": False, "error": str(e)}), 500 - + print(f"Error saving resume: {str(e)}") + return jsonify({"error": str(e)}), 500 finally: await db.disconnect() diff --git a/prisma/migrations/20250325042059_/migration.sql b/prisma/migrations/20250325042059_/migration.sql new file mode 100644 index 0000000..dae1922 --- /dev/null +++ b/prisma/migrations/20250325042059_/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - The `content` column on the `ResumeSection` table would be dropped and recreated. This will lead to data loss if there is data in the column. + +*/ +-- AlterTable +ALTER TABLE "ResumeSection" DROP COLUMN "content", +ADD COLUMN "content" JSONB[]; + +-- DropEnum +DROP TYPE "JobStatus"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 07c249c..0576b50 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -26,8 +26,8 @@ model ResumeSection { id String @id @default(uuid()) @db.Uuid userId String @db.Uuid user User @relation(fields: [userId], references: [id]) - sectionType String // @index // e.g., "info", "education", "skills" - content Json // Stores section-specific data + sectionType String // @index + content Json[] // Changed to JSON array type createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } From 43d8f7c6e9b5d736d5d3bd9ac34eed2db0c387cb Mon Sep 17 00:00:00 2001 From: shivang-16 Date: Tue, 25 Mar 2025 11:14:31 +0530 Subject: [PATCH 2/3] resume url update --- controllers/user.py | 33 ++++++++++------- .../migrations/20250325050828_/migration.sql | 2 + prisma/schema.prisma | 1 + utils/__init__.py | 3 +- utils/s3_utils.py | 37 ++++++++++++++++--- 5 files changed, 56 insertions(+), 20 deletions(-) create mode 100644 prisma/migrations/20250325050828_/migration.sql diff --git a/controllers/user.py b/controllers/user.py index 42c37da..281ed97 100644 --- a/controllers/user.py +++ b/controllers/user.py @@ -1,6 +1,6 @@ from db.prisma import db from flask import jsonify, request, Blueprint, g, send_file -from utils import serialize_job, extract_text, allowed_file, parse_resume +from utils import serialize_job, extract_text, allowed_file, parse_resume, process_resume_upload from function.insert_job import insert_job from datetime import datetime import json @@ -12,7 +12,6 @@ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib import colors -from utils.s3_utils import get_put_object_signed_url, get_object_signed_url user_blueprint = Blueprint('user', __name__) @@ -321,6 +320,8 @@ async def create_job(): @user_blueprint.route('/resume/upload', methods=['POST']) async def upload_resume(): + await db.connect() + if 'resume' not in request.files: return jsonify({'error': 'No file uploaded'}), 400 @@ -329,18 +330,14 @@ async def upload_resume(): return jsonify({'error': 'Invalid file type. Only PDF and DOCX are supported.'}), 400 try: - # Generate unique file key for S3 + extension = file.filename.rsplit('.', 1)[1].lower() - timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') - file_key = f"resumes/{g.user.id}/{timestamp}_{file.filename}" - - # Get presigned URL for upload - presigned_url = get_put_object_signed_url({ - 'Bucket': os.getenv('AWS_BUCKET_NAME'), - 'Key': file_key, - 'ContentType': f'application/{extension}' - }) + # Generate S3 URLs using utility function + s3_data = process_resume_upload(file, g.user.id) + + print(s3_data, "here is the s3 data") + # Process file content temp_dir = tempfile.mkdtemp() temp_path = os.path.join(temp_dir, file.filename) @@ -361,12 +358,19 @@ async def upload_resume(): parsed_data = parse_resume(text) + # Update user resume URL after successful processing + await db.user.update( + where={"id": g.user.id}, + data={"resumeUrl": s3_data['get_url']} + ) + # Add S3 information to response response_data = { "success": True, "parsed_data": parsed_data, - "upload_url": presigned_url, - "file_key": file_key + "upload_url": s3_data['put_url'], + "file_key": s3_data['file_key'], + "resume_url": s3_data['get_url'] } return jsonify(response_data), 200 @@ -377,6 +381,7 @@ async def upload_resume(): finally: # Cleanup temporary files + await db.disconnect() if 'temp_dir' in locals() and os.path.exists(temp_dir): for root, dirs, files in os.walk(temp_dir, topdown=False): for name in files: diff --git a/prisma/migrations/20250325050828_/migration.sql b/prisma/migrations/20250325050828_/migration.sql new file mode 100644 index 0000000..b0fe6a4 --- /dev/null +++ b/prisma/migrations/20250325050828_/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "resumeUrl" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0576b50..d126c41 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -19,6 +19,7 @@ model User { bookmarked_jobs String[] jobs Tracked_Jobs[] created_at DateTime @default(now()) + resumeUrl String? resume ResumeSection[] } diff --git a/utils/__init__.py b/utils/__init__.py index c5e4a96..76c681e 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1,5 +1,6 @@ from .setCookie import setCookie from .serialize_data import serialize_job from .resume_parser import extract_text, allowed_file, parse_resume +from .s3_utils import get_put_object_signed_url, get_object_signed_url, process_resume_upload -__all__ = ['setCookie', 'serialize_job', 'extract_text', 'allowed_file', 'parse_resume'] \ No newline at end of file +__all__ = ['setCookie', 'serialize_job', 'extract_text', 'allowed_file', 'parse_resume', 'get_put_object_signed_url', 'get_object_signed_url', 'process_resume_upload'] \ No newline at end of file diff --git a/utils/s3_utils.py b/utils/s3_utils.py index 93e299b..2b11478 100644 --- a/utils/s3_utils.py +++ b/utils/s3_utils.py @@ -1,6 +1,9 @@ from aws.s3 import s3_client from typing import TypedDict from botocore.exceptions import ClientError +from datetime import datetime +import os + class S3Info(TypedDict): Bucket: str @@ -24,18 +27,42 @@ def get_put_object_signed_url(info: S3Info) -> str: print(f"Error generating put signed URL: {e}") raise e -def get_object_signed_url(info: S3Info) -> str: +def get_object_signed_url(key: str) -> str: """Generate a presigned URL for downloading an object from S3""" try: url = s3_client.generate_presigned_url( 'get_object', Params={ - 'Bucket': info['Bucket'], - 'Key': info['Key'] + 'Bucket': os.getenv('AWS_BUCKET_NAME'), + 'Key': key }, - ExpiresIn=3600 # URL expires in 1 hour + ExpiresIn=3600 ) return url except ClientError as e: print(f"Error generating get signed URL: {e}") - raise e \ No newline at end of file + raise e + +def process_resume_upload(file, user_id): + """Handle S3 upload process for resume files""" + # Generate unique file key + extension = file.filename.rsplit('.', 1)[1].lower() + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + file_key = f"resumes/{user_id}/{timestamp}_{file.filename}" + + # Generate URLs + put_url = get_put_object_signed_url({ + 'Bucket': os.getenv('AWS_BUCKET_NAME'), + 'Key': file_key, + 'ContentType': f'application/{extension}' + }) + + get_url = get_object_signed_url(file_key) # Now correctly accepts just the key string + + return { + 'put_url': put_url, + 'get_url': get_url, + 'file_key': file_key, + 'file_name': file.filename, + 'content_type': f'application/{extension}' + } \ No newline at end of file From f126784b1fa294fd5b681e9d373f98408f2a6ecc Mon Sep 17 00:00:00 2001 From: rameshchandra8520 Date: Tue, 25 Mar 2025 13:10:10 +0530 Subject: [PATCH 3/3] refactor: update prisma content handling and user association in resume data --- controllers/user.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/controllers/user.py b/controllers/user.py index 281ed97..1f27099 100644 --- a/controllers/user.py +++ b/controllers/user.py @@ -405,9 +405,7 @@ async def save_resume_data(): if not isinstance(section_items, list): section_items = [section_items] - prisma_content = [item if isinstance(item, dict) else {"value": item} - for item in section_items] - + prisma_content = json.dumps(section_items) existing_section = await db.resumesection.find_first( where={ "userId": currentUser.id, @@ -420,7 +418,7 @@ async def save_resume_data(): where={"id": existing_section.id}, data={ "content": prisma_content, - "userId": currentUser.id # Direct scalar assignment + "user": {"connect": {"id": currentUser.id}} } ) else: @@ -428,7 +426,7 @@ async def save_resume_data(): data={ "sectionType": section_type, "content": prisma_content, - "userId": currentUser.id # Required scalar field + "user": {"connect": {"id": currentUser.id}} } ) created_sections.append(resume_section)