Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 94 additions & 75 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
from services.reminder_scheduler import start_scheduler
from services.complexity_analyzer import analyze_code
from social import share_to_platforms
from services.credential_service import resolve_user_credentials
from utils.crypto import encrypt
from models.user import PlatformCredential

load_dotenv()

Expand Down Expand Up @@ -458,90 +461,106 @@ async def create_blog(

user_settings = await _settings_for_user(user_id)

# --- Atomic Lock to prevent Race Conditions ---
lock_id = f"generate_blog_{problem.title}_{problem.author}_{user_email}"
try:
blog_content = await run_in_threadpool(generate_blog, problem, credentials=user_settings)
efficiency = rate_code_efficiency(
problem.title,
problem.code,
problem.language or "python"
)
except Exception as e:
return {"status": "error", "message": f"AI provider failure: {str(e)}"}

# Resolve platform-specific credentials from database securely at runtime
devto_creds = await resolve_user_credentials(db, user_id, "devto")
await db.locks.insert_one({"_id": lock_id, "timestamp": datetime.now(timezone.utc)})
except PyMongoError:
return {
"status": "error",
"message": f"Solution for '{problem.title}' has already been published!",
}

try:
suggested_tags = await run_in_threadpool(
generate_tags,
problem,
blog_content,
credentials=user_settings,
)
except Exception:
suggested_tags = ""

try:
platform_results = await publish_to_platforms(
problem.title,
blog_content,
platforms=problem.platforms or user_settings.get("publish_platforms"),
published=not problem.publish_as_draft,
tags=problem.tags,
credentials=devto_creds, # Using user specific keys
)
successful = [r for r in platform_results if r.get("status") == "success"]
overall_status = (
"success"
if len(successful) == len(platform_results)
else "partial_success" if successful else "error"
)
except Exception as e:
return {"status": "error", "message": f"Publishing failure: {str(e)}"}

try:
record = PublishRecord(
title=problem.title,
date=datetime.now(timezone.utc).isoformat(),
platforms=[r["platform"] for r in successful],
status=overall_status,
author=problem.author,
user_email=user_email,
)
try:
blog_content = await run_in_threadpool(generate_blog, problem, credentials=user_settings)
efficiency = rate_code_efficiency(
problem.title,
problem.code,
problem.language or "python"
)
except Exception as e:
return {"status": "error", "message": f"AI provider failure: {str(e)}"}

await db.problem_info.update_one(
{"title": problem.title, "author": problem.author, "user_email": user_email},
{"$set": record.model_dump()},
upsert=True,
)
# Resolve platform-specific credentials from database securely at runtime
devto_creds = await resolve_user_credentials(db, user_id, "devto")

except Exception as e:
print(f"Database logging failed: {e}")
try:
suggested_tags = await run_in_threadpool(
generate_tags,
problem,
blog_content,
credentials=user_settings,
)
except Exception:
suggested_tags = ""

social_results = []
if problem.share_to_social and successful:
post_url = next((res["url"] for res in successful if res.get("url")), None)
try:
platform_results = await publish_to_platforms(
problem.title,
blog_content,
platforms=problem.platforms or user_settings.get("publish_platforms"),
published=not problem.publish_as_draft,
tags=problem.tags,
credentials=devto_creds, # Using user specific keys
)
successful = [r for r in platform_results if r.get("status") == "success"]
overall_status = (
"success"
if len(successful) == len(platform_results)
else "partial_success" if successful else "error"
)
except Exception as e:
return {"status": "error", "message": f"Publishing failure: {str(e)}"}

if post_url:
try:
# Dynamically fetch encrypted LinkedIn credentials
linkedin_creds = await resolve_user_credentials(db, user_id, "linkedin")
social_results = share_to_platforms(
title=problem.title,
post_url=post_url,
tags=problem.tags,
credentials=linkedin_creds, # Decrypted user scope profile object
)
except Exception as e:
print(f"Social sharing failed: {e}")
return {
"status": overall_status,
"data": {
"blog_content": blog_content,"efficiency": efficiency,
"platforms": platform_results,
"social": social_results,
},
}
try:
record = PublishRecord(
title=problem.title,
date=datetime.now(timezone.utc).isoformat(),
platforms=[r["platform"] for r in successful],
status=overall_status,
author=problem.author,
user_email=user_email,
)

await db.problem_info.update_one(
{"title": problem.title, "author": problem.author, "user_email": user_email},
{"$set": record.model_dump()},
upsert=True,
)

except Exception as e:
print(f"Database logging failed: {e}")

social_results = []
if problem.share_to_social and successful:
post_url = next((res["url"] for res in successful if res.get("url")), None)

if post_url:
try:
# Dynamically fetch encrypted LinkedIn credentials
linkedin_creds = await resolve_user_credentials(db, user_id, "linkedin")
social_results = share_to_platforms(
title=problem.title,
post_url=post_url,
tags=problem.tags,
credentials=linkedin_creds, # Decrypted user scope profile object
)
except Exception as e:
print(f"Social sharing failed: {e}")
return {
"status": overall_status,
"data": {
"blog_content": blog_content,"efficiency": efficiency,
"platforms": platform_results,
"social": social_results,
},
}
finally:
# Release the lock so future attempts can proceed if this failed
await db.locks.delete_one({"_id": lock_id})


# -----------------------------
Expand Down
22 changes: 22 additions & 0 deletions backend/test_race.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import threading
import requests

def send_request():
payload = {
"title": "Race Condition Test",
"code": "print('hello')",
"author": "Anonymous Developer",
"publish_as_draft": True
}
# You might need to add authentication headers depending on your setup
response = requests.post("http://127.0.0.1:10000/generate-blog", json=payload)
print(f"Response: {response.json()}")

threads = []
for _ in range(5):
t = threading.Thread(target=send_request)
threads.append(t)
t.start()

for t in threads:
t.join()