From f0d04032b99ccd094ef7c288f035d8cd7022cd3f Mon Sep 17 00:00:00 2001 From: Barunes Padhy Date: Thu, 20 Jun 2024 18:22:20 +0300 Subject: [PATCH] Made changes to introduce SEO and made react SPA routing compatible with github pages --- backend/.gitignore | 1 + backend/apimanager/publish_methods.py | 45 +++++---- backend/apimanager/publish_methods_github.py | 75 +++++++-------- backend/apimanager/publish_views.py | 96 ++++++++++++++------ backend/backend/settings.py | 1 + backend/requirements.txt | 3 + frontend/viewable-ui/vite.config.js | 11 +-- 7 files changed, 139 insertions(+), 93 deletions(-) diff --git a/backend/.gitignore b/backend/.gitignore index 5fcd1f6..1fbbaea 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -15,6 +15,7 @@ deploy media static templates +start_editor.sh # If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ # in your Git repository. Update and uncomment the following line accordingly. diff --git a/backend/apimanager/publish_methods.py b/backend/apimanager/publish_methods.py index 0a05ac8..74174b0 100644 --- a/backend/apimanager/publish_methods.py +++ b/backend/apimanager/publish_methods.py @@ -11,18 +11,16 @@ from .dialogue_box import ( ) from .publish_methods_github import ( - create_404_page, git_existing_repo_setup, github_init, github_pages_deploy - ) deployment_methods = { - "server_deploy": { + "server": { "name": "Server Deploy" }, - "github_deploy": { + "ghpages": { "name": "Github Deploy" } } @@ -30,12 +28,7 @@ deployment_methods = { def server_deploy(): try: - copy_content( - settings.DEPLOY_CONFIG["EDITOR_DATA_LOCATION"], - f'{settings.DEPLOY_CONFIG["DEPLOY_LOCATION"]}/server/data', - 'folder', - 'remove_and_copy' - ) + copy_data_and_html('server') return {'message': 'Server deployment successful', 'status': status.HTTP_200_OK} except Exception as e: print(f"An error occurred: {str(e)}") @@ -60,18 +53,9 @@ def github_deploy(): } deploy_location = settings.DEPLOY_CONFIG["DEPLOY_LOCATION"]+'/ghpages' - - create_404_page(deploy_location) - copy_content( - settings.DEPLOY_CONFIG["EDITOR_DATA_LOCATION"], - f'{settings.DEPLOY_CONFIG["DEPLOY_LOCATION"]}/ghpages/data', - 'folder', - 'remove_and_copy' - ) - + copy_data_and_html('ghpages') if not os.path.exists(f'{deploy_location}/.git'): try: - existing_repo = draw_dialogue_box( 'Github Deploy', 'Do you have an existing repository with Rangolio on github?', @@ -94,3 +78,24 @@ def github_deploy(): except Exception as e: print(f"An error occurred: {str(e)}") return {'message': str(e), 'status': status.HTTP_500_INTERNAL_SERVER_ERROR} + + +def copy_data_and_html(deploy_type): + copy_content( + settings.DEPLOY_CONFIG["EDITOR_DATA_LOCATION"], + f'{settings.DEPLOY_CONFIG["DEPLOY_LOCATION"]}/{deploy_type}/data', + 'folder', + 'remove_and_copy' + ) + copy_content( + f'{settings.DEPLOY_CONFIG["EDITOR_HTML_LOCATION"]}/categories', + f'{settings.DEPLOY_CONFIG["DEPLOY_LOCATION"]}/{deploy_type}/categories', + 'folder', + 'remove_and_copy' + ) + copy_content( + f'{settings.DEPLOY_CONFIG["EDITOR_HTML_LOCATION"]}/blog', + f'{settings.DEPLOY_CONFIG["DEPLOY_LOCATION"]}/{deploy_type}/blog', + 'folder', + 'remove_and_copy' + ) diff --git a/backend/apimanager/publish_methods_github.py b/backend/apimanager/publish_methods_github.py index f9abf84..c4f576a 100644 --- a/backend/apimanager/publish_methods_github.py +++ b/backend/apimanager/publish_methods_github.py @@ -4,11 +4,14 @@ import shutil import subprocess import urllib.parse +from .utilities import ( + copy_content +) + from .dialogue_box import ( draw_dialogue_box ) - def github_init(deploy_location, git_commands): user_details_defined = git_check_user_details(deploy_location, git_commands) if not user_details_defined: @@ -74,15 +77,44 @@ def git_check_user_details(deploy_location, git_commands): def git_update_viewable_ui(deploy_location, dist_folder_name, build_frontend=False): shutil.move(deploy_location, f'{deploy_location}.temp') + if build_frontend: subprocess.run(["npm", 'run', 'build:ghpages'], cwd=settings.DEPLOY_CONFIG["VIEWABLE_UI_LOCATION"], check=True, text=True, capture_output=True) + shutil.move(f'{settings.DEPLOY_CONFIG["DEPLOY_LOCATION"]}/{dist_folder_name}', f'{deploy_location}') - shutil.copy(f'{deploy_location}.temp/index.html', deploy_location) - shutil.copy(f'{deploy_location}.temp/404.html', deploy_location) - shutil.copytree(f'{deploy_location}.temp/assets', f'{deploy_location}/assets', dirs_exist_ok=True) - if os.path.exists(f'{deploy_location}.temp/data'): - shutil.copytree(f'{deploy_location}.temp/data', f'{deploy_location}/data', dirs_exist_ok=True) + + copy_content( + f'{deploy_location}.temp/index.html', + deploy_location, + 'file', + 'remove_and_copy' + ) + copy_content( + f'{deploy_location}.temp/assets', + f'{deploy_location}/assets', + 'folder', + 'remove_and_copy' + ) + copy_content( + f'{deploy_location}.temp/data', + f'{deploy_location}/data', + 'folder', + 'remove_and_copy' + ) + copy_content( + f'{deploy_location}.temp/categories', + f'{deploy_location}/categories', + 'folder', + 'remove_and_copy' + ) + copy_content( + f'{deploy_location}.temp/blog', + f'{deploy_location}/blog', + 'folder', + 'remove_and_copy' + ) + shutil.rmtree(f'{deploy_location}.temp') @@ -124,34 +156,3 @@ def github_pages_deploy(deploy_location, git_commands): subprocess.run(git_commands["git_add"], cwd=deploy_location, check=True, text=True, capture_output=True) subprocess.run(git_commands["git_commit"], cwd=deploy_location, check=True, text=True, capture_output=True) subprocess.run(git_commands["git_push"], cwd=deploy_location, check=True, text=True, capture_output=True) - - -def create_404_page(deploy_location): - html_content = """ - - - - - Rangoio - - - - - - """ - - with open(f'{deploy_location}/404.html', 'w') as file: - file.write(html_content) - - print("404 page created successfully.") - \ No newline at end of file diff --git a/backend/apimanager/publish_views.py b/backend/apimanager/publish_views.py index 3e1ee33..8ad030f 100644 --- a/backend/apimanager/publish_views.py +++ b/backend/apimanager/publish_views.py @@ -7,6 +7,7 @@ from rest_framework.views import APIView from rest_framework.response import Response from django.conf import settings from django.core.files.base import ContentFile +from bs4 import BeautifulSoup from .custom_storage import ( CustomStorage @@ -38,55 +39,59 @@ class Publish(APIView): def get(self, request, deploy_type, format=None): if deploy_type not in deployment_methods: return Response(status=status.HTTP_404_NOT_FOUND) + json_storage_location = settings.DEPLOY_CONFIG["EDITOR_DATA_LOCATION"] + html_storage_location = settings.DEPLOY_CONFIG["EDITOR_HTML_LOCATION"] - storage = CustomStorage() - self.delete_old_data() - self.create_json(storage) + json_storage = CustomStorage(json_storage_location) + html_storage = CustomStorage(html_storage_location) + + self.delete_old_data(json_storage_location) + self.delete_old_data(html_storage_location) + + self.create_json_and_html(json_storage, html_storage, deploy_type) response = self.execute_deploy(deploy_type) return Response(response['message'], response['status']) - def delete_old_data(self): - data_directory = 'deploy/data' - + def delete_old_data(self, data_directory): if os.path.exists(data_directory): shutil.rmtree(data_directory) print(f"The directory {data_directory} has been deleted.") else: print(f"The directory {data_directory} does not exist.") - def create_json(self, storage): - self.create_user_data_json(UserData.objects.first(), storage) - self.create_theme_data_json(UserData.objects.first(), storage) - self.create_category_data_json(Category, storage) - self.create_blog_data_json(Blog.objects.all(), storage) + def create_json_and_html(self, json_storage, html_storage, deploy_type): + self.create_user_data_json(UserData.objects.first(), json_storage) + self.create_theme_data_json(UserData.objects.first(), json_storage) + self.create_category_data_json_and_html(Category, json_storage, html_storage, deploy_type) + self.create_blog_data_json_and_html(Blog.objects.all(), json_storage, html_storage, deploy_type, UserData.objects.first()) copy_content( settings.DEPLOY_CONFIG["EDITOR_MEDIA_LOCATION"], settings.DEPLOY_CONFIG["EDITOR_DATA_LOCATION"], 'folder', ) - def create_user_data_json(self, instance, storage): + def create_user_data_json(self, instance, json_storage): json_content = { "name": instance.name, "introContent": self.sanitize_media_link(instance.intro_content, 'content_media'), "profilePhoto": self.sanitize_media_link(instance.profile_photo), "builtWith": instance.built_with } - self.save_json(json_content, 'shared/user-data.json', storage) + self.save_json(json_content, 'shared/user-data.json', json_storage) - def create_theme_data_json(self, instance, storage): + def create_theme_data_json(self, instance, json_storage): json_content = { "defaultTheme": instance.default_theme, "darkTheme": ast.literal_eval(instance.dark_theme), "lightTheme": ast.literal_eval(instance.light_theme), } - self.save_json(json_content, 'shared/theme-config.json', storage) + self.save_json(json_content, 'shared/theme-config.json', json_storage) - def create_category_data_json(self, model_instance, storage): + def create_category_data_json_and_html(self, model_instance, json_storage, html_storage, deploy_type): categories = [] instance_objects = model_instance.objects.all() if not instance_objects.exists(): - self.save_json([], 'category/category-metadata.json', storage) + self.save_json([], 'category/category-metadata.json', json_storage) else: for eachInstance in instance_objects: instance_data = { @@ -98,10 +103,12 @@ class Publish(APIView): "featuredBlog": eachInstance.featured_id } categories.append(instance_data) - self.create_instance_data(instance_data, model_instance.objects.get(category_id=eachInstance.category_id), storage) - self.save_json(categories, 'category/category-metadata.json', storage) + self.create_instance_data(instance_data, model_instance.objects.get(category_id=eachInstance.category_id), json_storage) + self.save_json(categories, 'category/category-metadata.json', json_storage) + self.save_html(categories, 'categories', html_storage, deploy_type) - def create_blog_data_json(self, instance, storage): + def create_blog_data_json_and_html(self, instance, json_storage, html_storage, deploy_type, UserDataInstance): + blogs = [] if not instance.exists(): pass else: @@ -115,13 +122,15 @@ class Publish(APIView): "parentCategory": str(eachBlog.parent_category.category_id), "contentBody": self.sanitize_media_link(eachBlog.content_body, 'content_media') } - self.save_json(instance_data, f'blog/{instance_data["id"]}/blog-data.json', storage) + blogs.append(instance_data) + self.save_json(instance_data, f'blog/{instance_data["id"]}/blog-data.json', json_storage) + self.save_html(blogs, 'blog', html_storage, deploy_type, UserDataInstance) - def create_instance_data(self, instance_data, blogs_by_category_instance, storage): + def create_instance_data(self, instance_data, blogs_by_category_instance, json_storage): instance_data["blogMetadata"]=[] blogs = blogs_by_category_instance.blogs.all() if not blogs.exists(): - self.save_json(instance_data, f'category/{instance_data["id"]}/category-data.json', storage) + self.save_json(instance_data, f'category/{instance_data["id"]}/category-data.json', json_storage) else: for eachBlog in blogs: instance_data["blogMetadata"].append({ @@ -132,7 +141,7 @@ class Publish(APIView): "tagLine": eachBlog.tagline, "parentCategory": instance_data["id"] }) - self.save_json(instance_data, f'category/{instance_data["id"]}/category-data.json', storage) + self.save_json(instance_data, f'category/{instance_data["id"]}/category-data.json', json_storage) def save_json(self, json_content, file_name, storage): data_json = json.dumps(json_content, indent=2) @@ -140,6 +149,41 @@ class Publish(APIView): storage.delete(file_name) storage.save(file_name, ContentFile(data_json.encode('utf-8'))) + def save_html(self, json_content, resource_type, storage, deploy_type, UserDataInstance=None): + html_file = open(f'{settings.DEPLOY_CONFIG["DEPLOY_LOCATION"]}/{deploy_type}/index.html', "r") + html_file_content = html_file.read() + html_soup = BeautifulSoup(html_file_content, 'lxml') + + html_soup.title.string = resource_type + meta_robots = html_soup.new_tag('meta', attrs={'name': 'robots', 'content': 'index, follow'}) + html_soup.head.append(meta_robots) + + storage.save(f'{resource_type}/index.html', ContentFile(str(html_soup).encode('utf-8'))) + + for eachEntry in json_content: + html_soup = BeautifulSoup(html_file_content, 'lxml') + html_soup.title.string = eachEntry['name'] + meta_description = html_soup.new_tag('meta', attrs={'name': 'description', 'content': f'{eachEntry["name"]} {eachEntry["description"]} {eachEntry["tagLine"]}'}) + meta_robots = html_soup.new_tag('meta', attrs={'name': 'robots', 'content': 'index, follow'}) + meta_language = html_soup.new_tag('meta', attrs={'name': 'language', 'content': 'english'}) + + meta_og_description = html_soup.new_tag('meta', attrs={'name': 'og:description', 'content': f'{eachEntry["name"]} {eachEntry["description"]} {eachEntry["tagLine"]}'}) + meta_og_title = html_soup.new_tag('meta', attrs={'name': 'og:title', 'content': eachEntry['name']}) + meta_og_type = html_soup.new_tag('meta', attrs={'name': 'og:type', 'content': 'website'}) + + html_soup.head.append(meta_description) + html_soup.head.append(meta_robots) + html_soup.head.append(meta_language) + html_soup.head.append(meta_og_description) + html_soup.head.append(meta_og_title) + html_soup.head.append(meta_og_type) + + if UserDataInstance: + meta_author = html_soup.new_tag('meta', attrs={'name': 'author', 'content': UserDataInstance.name}) + html_soup.head.append(meta_author) + + storage.save(f'{resource_type}/{eachEntry["id"]}/index.html', ContentFile(str(html_soup).encode('utf-8'))) + def sanitize_media_link(self, string, content_type='element'): if not string: return '' @@ -154,9 +198,9 @@ class Publish(APIView): 'status': status.HTTP_500_INTERNAL_SERVER_ERROR } - if deploy_type == "server_deploy": + if deploy_type == "server": response = server_deploy() - if deploy_type == "github_deploy": + if deploy_type == "ghpages": response = github_deploy() return response diff --git a/backend/backend/settings.py b/backend/backend/settings.py index 1419ecf..99891d4 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -44,6 +44,7 @@ INSTALLED_APPS = [ DEPLOY_CONFIG = { "VIEWABLE_UI_LOCATION": os.path.join(BASE_DIR, '../frontend/viewable-ui'), "DEPLOY_LOCATION": os.path.join(BASE_DIR, '../frontend/viewable-ui/dist'), + "EDITOR_HTML_LOCATION": os.path.join(BASE_DIR, 'deploy/html'), "EDITOR_DATA_LOCATION": os.path.join(BASE_DIR, 'deploy/data'), "EDITOR_MEDIA_LOCATION": os.path.join(BASE_DIR, 'media/data') } diff --git a/backend/requirements.txt b/backend/requirements.txt index bc2a09c..2a7b902 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,13 +1,16 @@ asgiref==3.8.1 +beautifulsoup4==4.12.3 bottle==0.12.25 Django==5.0.6 djangorestframework==3.15.1 gunicorn==22.0.0 +lxml==5.2.2 packaging==24.0 pillow==10.3.0 proxy_tools==0.1.0 PyQt6==6.7.0 PyQt6-Qt6==6.7.1 PyQt6-sip==13.6.0 +soupsieve==2.5 sqlparse==0.5.0 typing_extensions==4.12.2 diff --git a/frontend/viewable-ui/vite.config.js b/frontend/viewable-ui/vite.config.js index 5a4872b..9cf974f 100644 --- a/frontend/viewable-ui/vite.config.js +++ b/frontend/viewable-ui/vite.config.js @@ -6,16 +6,7 @@ import eslint from 'vite-plugin-eslint'; export default defineConfig({ plugins: [ react(), - eslint(), - process.env.BUILD_ENV === 'ghpages' ? { - name: 'inject-ghpages-fix', - transformIndexHtml(html) { - return html.replace( - '
', - '
' - ); - } - } : '' + eslint() ], server: {}, build: {