diff --git a/src/Makefile b/src/Makefile index 690e37f61..fbbac2d3f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -24,6 +24,8 @@ build-and-push-prod: docker build -t massenergize/api . docker tag massenergize/api:latest 202758212688.dkr.ecr.us-east-2.amazonaws.com/massenergize/api:$(VERSION) docker push 202758212688.dkr.ecr.us-east-2.amazonaws.com/massenergize/api:$(VERSION) + + eb setenv DJANGO_ENV=prod eb deploy --label $(VERSION) git tag prod@$(VERSION) @@ -41,6 +43,8 @@ build-and-push-canary: docker build -t massenergize/api-canary . docker tag massenergize/api-canary:latest 202758212688.dkr.ecr.us-east-2.amazonaws.com/massenergize/api-canary:$(CANARY_VERSION) docker push 202758212688.dkr.ecr.us-east-2.amazonaws.com/massenergize/api-canary:$(CANARY_VERSION) + + eb setenv DJANGO_ENV=canary eb deploy --label $(CANARY_VERSION) git tag canary@$(CANARY_VERSION) @@ -58,6 +62,8 @@ build-and-push-dev: docker build -t massenergize/api-dev . docker tag massenergize/api-dev:latest 202758212688.dkr.ecr.us-east-2.amazonaws.com/massenergize/api-dev:$(DEV_VERSION) docker push 202758212688.dkr.ecr.us-east-2.amazonaws.com/massenergize/api-dev:$(DEV_VERSION) + + eb setenv DJANGO_ENV=dev eb deploy --label $(DEV_VERSION) git tag dev@$(DEV_VERSION) diff --git a/src/_main_/settings.py b/src/_main_/settings.py index ac4e212ad..729027a60 100644 --- a/src/_main_/settings.py +++ b/src/_main_/settings.py @@ -27,12 +27,12 @@ # ******** LOAD CONFIG DATA ***********# # DJANGO_ENV can be passed in through the makefile, with "make start env=local" -DJANGO_ENV = os.environ.get("DJANGO_ENV","remote") +DJANGO_ENV = os.environ.get("DJANGO_ENV", "dev").lower() # Database selection, development DB unless one of these chosen -IS_PROD = False -IS_CANARY = False -IS_LOCAL = False +IS_PROD = DJANGO_ENV == 'prod' +IS_CANARY = DJANGO_ENV == 'canary' +IS_LOCAL = DJANGO_ENV == 'local' RUN_SERVER_LOCALLY = IS_LOCAL RUN_CELERY_LOCALLY = IS_LOCAL @@ -40,6 +40,8 @@ if is_test_mode(): RUN_CELERY_LOCALLY = True + + try: if IS_PROD: env_path = Path('.') / 'prod.env' diff --git a/src/api/handlers/event.py b/src/api/handlers/event.py index b8bb93742..c840b49f1 100644 --- a/src/api/handlers/event.py +++ b/src/api/handlers/event.py @@ -64,7 +64,8 @@ def info(self, request): def copy(self, request): context: Context = request.context args: dict = context.args - + + self.validator.rename("id", "event_id") self.validator.expect("event_id", int, is_required=True) args, err = self.validator.verify(args, strict=True) diff --git a/src/api/handlers/goal.py b/src/api/handlers/goal.py index dbea7a487..817f74871 100644 --- a/src/api/handlers/goal.py +++ b/src/api/handlers/goal.py @@ -37,7 +37,7 @@ def info(self, request): context: Context = request.context args: dict = context.args - goal_id = args.get('goal_id') + goal_id = args.get('goal_id', args.get('id', None)) goal_info, err = self.service.get_goal_info(goal_id) if err: return err diff --git a/src/api/handlers/misc.py b/src/api/handlers/misc.py index 8ed1ecb19..38c4901f8 100644 --- a/src/api/handlers/misc.py +++ b/src/api/handlers/misc.py @@ -7,6 +7,9 @@ from api.decorators import admins_only, super_admins_only from database.utils.settings.admin_settings import AdminPortalSettings from database.utils.settings.user_settings import UserPortalSettings +from concurrent.futures import ThreadPoolExecutor +import requests +from django.urls import reverse class MiscellaneousHandler(RouteHandler): @@ -17,7 +20,7 @@ def __init__(self): def registerRoutes(self) -> None: self.add("/menus.remake", self.remake_navigation_menu) - self.add("/menus.list", self.navigation_menu_list) + self.add("/menus.list", self.load_menu_items) #TODO: revert and create new enpoint eg user.portal.menu.load self.add("/data.backfill", self.backfill) self.add("/data.carbonEquivalency.create", self.create_carbon_equivalency) self.add("/data.carbonEquivalency.update", self.update_carbon_equivalency) @@ -32,6 +35,7 @@ def registerRoutes(self) -> None: self.add("/settings.list", self.fetch_available_preferences) self.add("/what.happened", self.fetch_footages) self.add("/actions.report", self.actions_report) + self.add("/site.load", self.load_essential_initial_site_data) @admins_only def fetch_footages(self, request): @@ -157,3 +161,50 @@ def authenticateFrontendInTestMode(self, request): "token", value=token, max_age=24 * 60 * 60, samesite="Strict" ) return response + + + def load_essential_initial_site_data(self, request): + context: Context = request.context + args: dict = context.args + + self.validator.expect("community_id", is_required=False) + self.validator.expect("subdomain", is_required=False) + self.validator.expect("endpoints", 'str_list', is_required=True) + self.validator.expect("id", is_required=False) + + args, err = self.validator.verify(args, strict=True) + if err: + return err + + endpoints = args.pop("endpoints", []) + + def fetch_data(endpoint): + endpoint = request.build_absolute_uri(endpoint) + response = requests.post(endpoint,data=args, cookies=request.COOKIES) + return response.json() + + # Use a ThreadPoolExecutor to make requests to all endpoints concurrently + with ThreadPoolExecutor(max_workers=5) as executor: + results = executor.map(fetch_data, endpoints) + + # Convert the results to a dictionary in the format {endpoint: data} + data = {endpoint: result for endpoint, result in zip(endpoints, results)} + + return MassenergizeResponse(data=data) + + + def load_menu_items(self, request): + context: Context = request.context + args: dict = context.args + + self.validator.expect("community_id", is_required=False) + self.validator.expect("subdomain", is_required=False) + + args, err = self.validator.verify(args, strict=True) + if err: + return MassenergizeResponse(error=err) + + data, err = self.service.load_menu_items(context, args) + if err: + return err + return MassenergizeResponse(data=data) diff --git a/src/api/handlers/policy.py b/src/api/handlers/policy.py index e013b3c4f..59d8b7222 100644 --- a/src/api/handlers/policy.py +++ b/src/api/handlers/policy.py @@ -32,7 +32,7 @@ def registerRoutes(self): def info(self, request): context: Context = request.context args: dict = context.args - policy_id = args.pop('policy_id', None) + policy_id = args.pop('policy_id', args.pop('id', None)) policy_info, err = self.service.get_policy_info(policy_id) if err: return err diff --git a/src/api/handlers/subscriber.py b/src/api/handlers/subscriber.py index 7018c0e99..57eed7444 100644 --- a/src/api/handlers/subscriber.py +++ b/src/api/handlers/subscriber.py @@ -35,7 +35,7 @@ def registerRoutes(self): def info(self, request): context: Context = request.context args: dict = context.args - subscriber_id = args.pop('subscriber_id', None) + subscriber_id = args.pop('subscriber_id', args.pop('id', None)) if subscriber_id and not isinstance(subscriber_id, int): subscriber_id = parse_int(subscriber_id) subscriber_info, err = self.service.get_subscriber_info(subscriber_id) diff --git a/src/api/handlers/vendor.py b/src/api/handlers/vendor.py index 62946ff80..2c5a30671 100644 --- a/src/api/handlers/vendor.py +++ b/src/api/handlers/vendor.py @@ -41,6 +41,10 @@ def registerRoutes(self): def info(self, request): context: Context = request.context args = context.get_request_body() + + self.validator.rename("id", "vendor_id") + self.validator.expect("vendor_id", int, is_required=False) + args = rename_field(args, 'vendor_id', 'id') vendor_info, err = self.service.get_vendor_info(context, args) if err: diff --git a/src/api/services/misc.py b/src/api/services/misc.py index f1fa894f6..bd8d54d31 100644 --- a/src/api/services/misc.py +++ b/src/api/services/misc.py @@ -141,3 +141,12 @@ def authenticateFrontendInTestMode(self, args): return None, CustomMassenergizeError(str(err)) client = Client() return signinAs(client, user), None + + def load_menu_items(self,context, args): + res, err = self.store.load_menu_items(context,args) + if err: + return None, err + + return res, None + + diff --git a/src/api/store/community.py b/src/api/store/community.py index c4c1204f3..7bc1db2a7 100644 --- a/src/api/store/community.py +++ b/src/api/store/community.py @@ -1366,7 +1366,7 @@ def list_communities_feature_flags(self, context, args) -> Tuple[list, MassEnerg (Q(audience=FeatureFlagConstants().for_all_except()) & ~Q(communities__in=communities)) ).exclude(expires_on__lt=datetime.now()).prefetch_related('communities') - return feature_flags, None + return feature_flags.distinct(), None except Exception as e: return None, CustomMassenergizeError(str(e)) diff --git a/src/api/store/misc.py b/src/api/store/misc.py index 81088471e..75fc1fa8d 100644 --- a/src/api/store/misc.py +++ b/src/api/store/misc.py @@ -1,36 +1,21 @@ +from typing import Tuple + +from sentry_sdk import capture_message + +from _main_.utils.context import Context from _main_.utils.footage.spy import Spy -from api.tests.common import createUsers -from database.models import ( - Action, - Vendor, - Subdomain, - Event, - Community, - Menu, - Team, - TeamMember, - CommunityMember, - RealEstateUnit, - CommunityAdminGroup, - UserProfile, - Data, - TagCollection, - UserActionRel, - Data, - Location, - HomePageSettings, -) from _main_.utils.massenergize_errors import ( CustomMassenergizeError, InvalidResourceError, MassEnergizeAPIError, ) -from _main_.utils.context import Context +from api.tests.common import createUsers +from api.utils.api_utils import get_viable_menu_items +from database.models import Action, CarbonEquivalency, Community, CommunityAdminGroup, CommunityMember, Data, Event, \ + HomePageSettings, Location, Menu, RealEstateUnit, Subdomain, TagCollection, Team, TeamMember, UserActionRel, \ + UserProfile, Vendor from database.utils.common import json_loader -from database.models import CarbonEquivalency -from .utils import find_reu_community, split_location_string, check_location -from sentry_sdk import capture_message -from typing import Tuple +from .utils import check_location, find_reu_community, get_community, split_location_string class MiscellaneousStore: @@ -492,3 +477,23 @@ def list_commonly_used_icons(self): sorted_keys = sorted(common_icons, key=common_icons.get, reverse=True) for key in sorted_keys: print(str(key) + ": " + str(common_icons[key])) + + def load_menu_items(self, context, args): + try: + page = args.get("page", None) + subdonain = args.get("subdomain", None) + community_id = args.get("community_id", None) + user_id = args.get("user_id", None) + + if not subdonain and not community_id: + return None, CustomMassenergizeError("No community or subdomain provided") + + community, _ = get_community(community_id=community_id, subdomain=subdonain) + if not community: + return None, CustomMassenergizeError("Community not found") + + menu = get_viable_menu_items(community) + + return menu, None + except Exception as e: + return None, CustomMassenergizeError(e) diff --git a/src/api/utils/api_utils.py b/src/api/utils/api_utils.py index 14e7500e4..3fc63f7a6 100644 --- a/src/api/utils/api_utils.py +++ b/src/api/utils/api_utils.py @@ -1,6 +1,16 @@ +from datetime import datetime,timedelta from math import atan2, cos, radians, sin, sqrt -from database.models import Community, CommunityAdminGroup, Media, UserProfile -import pyshorteners + +from django.db.models import Q + +from _main_.utils.common import serialize_all +from _main_.utils.feature_flags.FeatureFlagConstants import FeatureFlagConstants +from database.models import AboutUsPageSettings, Action, ActionsPageSettings, Community, CommunityAdminGroup, \ + ContactUsPageSettings, Event, EventsPageSettings, FeatureFlag, ImpactPageSettings, Media, Menu, \ + Policy, TagCollection, Team, TeamsPageSettings, Testimonial, TestimonialsPageSettings, UserProfile, \ + Vendor, VendorsPageSettings +from database.utils.settings.admin_settings import AdminPortalSettings +from database.utils.settings.user_settings import UserPortalSettings def is_admin_of_community(context, community_id): @@ -98,3 +108,88 @@ def create_media_file(file, name): media = Media.objects.create(name=name, file=file) media.save() return media + + +class DonationPageSettings: + pass + +# -------------------------- Menu Utils -------------------------- +def prependPrefixToLinks(menu_item, prefix): + if not menu_item: + return None + if "link" in menu_item: + menu_item["link"] = "/" + prefix + menu_item["link"] + if "children" in menu_item: + for child in menu_item["children"]: + prependPrefixToLinks(child, prefix) + return menu_item + +def modify_menu_items_if_published(menu_items, page_settings, prefix): + if not menu_items or not page_settings or not prefix: + return [] + + main_menu = [] + + for item in menu_items: + if not item.get("children"): + name = item.get("link", "").strip("/") + if name in page_settings and not page_settings[name]: + main_menu.remove(item) + else: + for child in item["children"]: + name = child.get("link", "").strip("/") + if name in page_settings and not page_settings[name]: + item["children"].remove(child) + + for item in menu_items: + f = prependPrefixToLinks(item,prefix) + main_menu.append(f) + + return main_menu + +def get_viable_menu_items(community): + + about_us_page_settings = AboutUsPageSettings.objects.filter(community=community).first() + events_page_settings = EventsPageSettings.objects.filter(community=community).first() + impact_page_settings = ImpactPageSettings.objects.filter(community=community).first() + actions_page_settings = ActionsPageSettings.objects.filter(community=community).first() + contact_us_page_settings = ContactUsPageSettings.objects.filter(community=community).first() + teams_page_settings = TeamsPageSettings.objects.filter(community=community).first() + testimonial_page_settings = TestimonialsPageSettings.objects.filter(community=community).first() + vendors_page_settings = VendorsPageSettings.objects.filter(community=community).first() + donation_page_settings = DonationPageSettings.objects.filter(community=community).first() + + + menu_items = {} + all_menu = Menu.objects.all() + + nav_menu = all_menu.get(name="PortalMainNavLinks") + + portal_main_nav_links = modify_menu_items_if_published(nav_menu.content, { + "impact": impact_page_settings.is_published, + "aboutus": about_us_page_settings.is_published, + "contactus": contact_us_page_settings.is_published, + "actions": actions_page_settings.is_published, + "services": vendors_page_settings.is_published, + "testimonials": testimonial_page_settings.is_published, + "teams": teams_page_settings.is_published, + "events": events_page_settings.is_published, + "donate": donation_page_settings.is_published + }, community.subdomain) + + footer_menu_content = all_menu.get(name='PortalFooterQuickLinks') + + portal_footer_quick_links = [ + {**item, "link": "/"+community.subdomain + "/" + item["link"]} + if not item.get("children") and item.get("navItemId", None) != "footer-report-a-bug-id" + else item + for item in footer_menu_content.content["links"] + ] + portal_footer_contact_info = all_menu.get(name='PortalFooterContactInfo') + return [ + {**nav_menu.simple_json(), "content": portal_main_nav_links}, + {**footer_menu_content.simple_json(), "content": {"links": portal_footer_quick_links}}, + portal_footer_contact_info.simple_json() + + ] +# -------------------------- Menu Utils -------------------------- \ No newline at end of file diff --git a/src/confirm.sh b/src/confirm.sh deleted file mode 100644 index a4f4cba0b..000000000 --- a/src/confirm.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -# Ask for the user's confirmation -echo "Are you sure you want to execute the make command? (y/n)" -read confirm - -# Convert the input to lower case -confirm=$(echo $confirm | tr '[:upper:]' '[:lower:]') - -# Check if the user confirmed -if [[ $confirm == 'y' || $confirm == 'yes' ]]; then - # Execute the make command - make $@ -else - # Inform the user that the operation was cancelled - echo "Operation cancelled." -fi \ No newline at end of file