|
3 | 3 | from django.shortcuts import get_object_or_404 |
4 | 4 | from django.template.loader import render_to_string |
5 | 5 | from django.utils import timezone |
| 6 | +from django_q.tasks import async_task |
6 | 7 | from ninja import NinjaAPI |
7 | 8 |
|
8 | 9 | from core.api.auth import session_auth, superuser_api_auth |
|
28 | 29 | ProjectScanIn, |
29 | 30 | ProjectScanOut, |
30 | 31 | SubmitFeedbackIn, |
| 32 | + SubmitSitemapIn, |
31 | 33 | ToggleAutoSubmissionOut, |
32 | 34 | ToggleProjectKeywordUseIn, |
33 | 35 | ToggleProjectKeywordUseOut, |
| 36 | + ToggleProjectPageAlwaysUseIn, |
| 37 | + ToggleProjectPageAlwaysUseOut, |
34 | 38 | UpdateArchiveStatusIn, |
| 39 | + UpdateSitemapUrlIn, |
| 40 | + UpdateSitemapUrlOut, |
35 | 41 | UpdateTitleScoreIn, |
36 | 42 | UserSettingsOut, |
37 | 43 | ValidateUrlIn, |
@@ -406,6 +412,92 @@ def toggle_auto_submission(request: HttpRequest, project_id: int): |
406 | 412 | return {"status": "success", "enabled": project.enable_automatic_post_submission} |
407 | 413 |
|
408 | 414 |
|
| 415 | +@api.post("/projects/update-sitemap-url", response=UpdateSitemapUrlOut, auth=[session_auth]) |
| 416 | +def update_sitemap_url(request: HttpRequest, data: UpdateSitemapUrlIn): |
| 417 | + """ |
| 418 | + Update the sitemap URL for a project. When a sitemap URL is added or updated, |
| 419 | + it triggers automatic parsing and analysis of the sitemap pages. |
| 420 | + """ |
| 421 | + profile = request.auth |
| 422 | + project = get_object_or_404(Project, id=data.project_id, profile=profile) |
| 423 | + |
| 424 | + sitemap_url = data.sitemap_url.strip() |
| 425 | + |
| 426 | + if not sitemap_url: |
| 427 | + return { |
| 428 | + "status": "error", |
| 429 | + "message": "Sitemap URL cannot be empty", |
| 430 | + } |
| 431 | + |
| 432 | + if not sitemap_url.startswith(("http://", "https://")): |
| 433 | + return { |
| 434 | + "status": "error", |
| 435 | + "message": "Sitemap URL must start with http:// or https://", |
| 436 | + } |
| 437 | + |
| 438 | + logger.info( |
| 439 | + "[Update Sitemap URL] Updating sitemap URL for project", |
| 440 | + project_id=project.id, |
| 441 | + profile_id=profile.id, |
| 442 | + sitemap_url=sitemap_url, |
| 443 | + ) |
| 444 | + |
| 445 | + project.sitemap_url = sitemap_url |
| 446 | + project.save(update_fields=["sitemap_url"]) |
| 447 | + |
| 448 | + # Trigger sitemap parsing task |
| 449 | + async_task("core.tasks.parse_sitemap_and_save_urls", project.id, group="Parse Sitemap") |
| 450 | + |
| 451 | + return { |
| 452 | + "status": "success", |
| 453 | + "message": "Sitemap URL updated successfully. Pages will be analyzed in batches of 10.", |
| 454 | + } |
| 455 | + |
| 456 | + |
| 457 | +@api.post( |
| 458 | + "/project/{project_id}/sitemap/submit/", response=UpdateSitemapUrlOut, auth=[session_auth] |
| 459 | +) |
| 460 | +def submit_sitemap(request: HttpRequest, project_id: int, data: SubmitSitemapIn): |
| 461 | + """ |
| 462 | + Submit/update the sitemap URL for a project. When a sitemap URL is added or updated, |
| 463 | + it triggers automatic parsing and analysis of the sitemap pages. |
| 464 | + """ # noqa: E501 |
| 465 | + profile = request.auth |
| 466 | + project = get_object_or_404(Project, id=project_id, profile=profile) |
| 467 | + |
| 468 | + sitemap_url = data.sitemap_url.strip() |
| 469 | + |
| 470 | + if not sitemap_url: |
| 471 | + return { |
| 472 | + "status": "error", |
| 473 | + "message": "Sitemap URL cannot be empty", |
| 474 | + } |
| 475 | + |
| 476 | + if not sitemap_url.startswith(("http://", "https://")): |
| 477 | + return { |
| 478 | + "status": "error", |
| 479 | + "message": "Sitemap URL must start with http:// or https://", |
| 480 | + } |
| 481 | + |
| 482 | + logger.info( |
| 483 | + "[Submit Sitemap] Submitting sitemap URL for project", |
| 484 | + project_id=project.id, |
| 485 | + profile_id=profile.id, |
| 486 | + sitemap_url=sitemap_url, |
| 487 | + ) |
| 488 | + |
| 489 | + project.sitemap_url = sitemap_url |
| 490 | + project.save(update_fields=["sitemap_url"]) |
| 491 | + |
| 492 | + # Trigger sitemap parsing task |
| 493 | + async_task("core.tasks.parse_sitemap_and_save_urls", project.id, group="Parse Sitemap") |
| 494 | + |
| 495 | + return { |
| 496 | + "status": "success", |
| 497 | + "message": "Sitemap submitted successfully! Your pages will be analyzed shortly.", |
| 498 | + } |
| 499 | + |
| 500 | + |
409 | 501 | @api.post("/update-title-score/{suggestion_id}", response={200: dict}, auth=[session_auth]) |
410 | 502 | def update_title_score(request: HttpRequest, suggestion_id: int, data: UpdateTitleScoreIn): |
411 | 503 | profile = request.auth |
@@ -554,6 +646,7 @@ def user_settings(request: HttpRequest, project_id: int): |
554 | 646 | project_data = { |
555 | 647 | "name": project.name, |
556 | 648 | "url": project.url, |
| 649 | + "sitemap_url": project.sitemap_url, |
557 | 650 | "has_auto_submission_setting": project.has_auto_submission_setting, |
558 | 651 | } |
559 | 652 | data = {"profile": profile_data, "project": project_data} |
@@ -780,7 +873,14 @@ def submit_blog_post(request: HttpRequest, data: BlogPostIn): |
780 | 873 | ) |
781 | 874 | return BlogPostOut(status="success", message="Blog post submitted successfully.") |
782 | 875 | except Exception as e: |
783 | | - return BlogPostOut(status="error", message=f"Failed to submit blog post: {str(e)}") |
| 876 | + logger.error( |
| 877 | + "[Submit Blog Post] Failed to submit blog post", |
| 878 | + error=str(e), |
| 879 | + exc_info=True, |
| 880 | + title=data.title, |
| 881 | + slug=data.slug, |
| 882 | + ) |
| 883 | + return BlogPostOut(status="error", message="Failed to submit blog post") |
784 | 884 |
|
785 | 885 |
|
786 | 886 | @api.post("/post-generated-blog-post", response=PostGeneratedBlogPostOut, auth=[session_auth]) |
@@ -846,3 +946,39 @@ def fix_generated_blog_post(request: HttpRequest, data: FixGeneratedBlogPostIn): |
846 | 946 | exc_info=True, |
847 | 947 | ) |
848 | 948 | return {"status": "error", "message": f"Failed to fix blog post: {str(e)}"} |
| 949 | + |
| 950 | + |
| 951 | +@api.post( |
| 952 | + "/project-pages/toggle-always-use", response=ToggleProjectPageAlwaysUseOut, auth=[session_auth] |
| 953 | +) |
| 954 | +def toggle_project_page_always_use(request: HttpRequest, data: ToggleProjectPageAlwaysUseIn): |
| 955 | + """ |
| 956 | + Toggle the always_use field for a ProjectPage. |
| 957 | + When enabled, the page link will always be included in generated blog posts. |
| 958 | + """ # noqa: E501 |
| 959 | + profile = request.auth |
| 960 | + |
| 961 | + try: |
| 962 | + project_page = get_object_or_404(ProjectPage, id=data.page_id, project__profile=profile) |
| 963 | + |
| 964 | + project_page.always_use = not project_page.always_use |
| 965 | + project_page.save(update_fields=["always_use"]) |
| 966 | + |
| 967 | + return { |
| 968 | + "status": "success", |
| 969 | + "always_use": project_page.always_use, |
| 970 | + } |
| 971 | + |
| 972 | + except Exception as error: |
| 973 | + logger.error( |
| 974 | + "Failed to toggle ProjectPage always_use field", |
| 975 | + error=str(error), |
| 976 | + exc_info=True, |
| 977 | + page_id=data.page_id, |
| 978 | + profile_id=profile.id, |
| 979 | + ) |
| 980 | + return { |
| 981 | + "status": "error", |
| 982 | + "always_use": False, |
| 983 | + "message": f"Failed to toggle always use: {str(error)}", |
| 984 | + } |
0 commit comments