Skip to content
Merged
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
96 changes: 96 additions & 0 deletions apps/project/firebase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import logging
import typing

from celery import shared_task
from django.utils import timezone
from firebase_admin.db import Reference as FbReference
from pyfirebase_mapswipe import models as firebase_models
from pyfirebase_mapswipe import utils as firebase_utils

from apps.project.models import FirebasePushStatusEnum, Organization
from main.config import Config
from main.logging import log_extra

logger = logging.getLogger(__name__)


class InvalidOrganizationPushException(Exception): ...


def handle_new_organization_on_firebase(organization: Organization, organization_ref: FbReference):
organization_data = firebase_models.FbOrganisation(
name=organization.name,
nameKey="",
description=organization.description if organization.description else firebase_models.UNDEFINED,
abbreviation=organization.abbreviation if organization.abbreviation else firebase_models.UNDEFINED,
isArchived=organization.is_archived,
)

organization_ref.set(
value={
**firebase_utils.serialize(organization_data),
},
)


def handle_organization_update_on_firebase(organization: Organization, organization_ref: FbReference):
organization_ref.update(
value=firebase_utils.serialize(
firebase_models.FbOrganisation(
name=organization.name,
nameKey="",
description=organization.description if organization.description else firebase_models.UNDEFINED,
abbreviation=organization.abbreviation if organization.abbreviation else firebase_models.UNDEFINED,
isArchived=organization.is_archived,
),
),
)


@shared_task
def push_organization_to_firebase(organization_id: int):
organization = Organization.objects.filter(id=organization_id).first()
if not organization:
return
organization.update_firebase_push_status(FirebasePushStatusEnum.PROCESSING)

try:
organization_ref = Config.FIREBASE_HELPER.ref(
Config.FirebaseKeys.organization(organization.id),
)
fb_organization: typing.Any = organization_ref.get()

if not organization.firebase_last_pushed:
if fb_organization is not None:
logger.error(
"push_to_firebase found a organization already in firebase when creating a organization",
extra=log_extra({"organization": organization.pk}),
)
raise InvalidOrganizationPushException
handle_new_organization_on_firebase(organization, organization_ref)
else:
if fb_organization is None:
logger.error(
"push_to_firebase did not find organization in firebase when updating a organization",
extra=log_extra({"organization": organization.pk}),
)
raise InvalidOrganizationPushException
handle_organization_update_on_firebase(organization, organization_ref)
except InvalidOrganizationPushException:
organization.update_firebase_push_status(FirebasePushStatusEnum.FAILED)
except Exception:
logger.error(
"push_to_firebase failed",
extra=log_extra({"organization": organization.pk}),
exc_info=True,
)
organization.update_firebase_push_status(FirebasePushStatusEnum.FAILED)
else:
organization.firebase_last_pushed = timezone.now()
organization.update_firebase_push_status(FirebasePushStatusEnum.SUCCESS, commit=False)
organization.save(
update_fields=[
"firebase_last_pushed",
"firebase_push_status",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 5.2.4 on 2025-07-25 09:42

import apps.project.models
import django_choices_field.fields
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('project', '0008_merge_20250722_0437'),
]

operations = [
migrations.AddField(
model_name='organization',
name='firebase_last_pushed',
field=models.DateTimeField(blank=True, help_text='The latest time when organization was pushed to firebase', null=True),
),
migrations.AddField(
model_name='organization',
name='firebase_push_status',
field=django_choices_field.fields.IntegerChoicesField(blank=True, choices=[(1, 'Pending'), (2, 'Processing'), (3, 'Success'), (4, 'Failed')], choices_enum=apps.project.models.FirebasePushStatusEnum, null=True),
),
]
18 changes: 18 additions & 0 deletions apps/project/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,28 @@ class Organization(UserResource, ArchivableResource): # type: ignore[reportInco
unique=True,
)

# FIREBASE FIELDS

firebase_push_status: int | None = IntegerChoicesField( # type: ignore[reportAssignmentType]
choices_enum=FirebasePushStatusEnum,
null=True,
blank=True,
)
firebase_last_pushed = models.DateTimeField(
null=True,
blank=True,
help_text=gettext_lazy("The latest time when organization was pushed to firebase"),
)

@typing.override
def __str__(self) -> str:
return self.name

def update_firebase_push_status(self, firebase_push_status: FirebasePushStatusEnum, *, commit: bool = True):
self.firebase_push_status = firebase_push_status
if commit:
self.save(update_fields=("firebase_push_status",))


class Project(UserResource):
Type = ProjectTypeEnum
Expand Down
13 changes: 13 additions & 0 deletions apps/project/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from rest_framework import serializers

from apps.common.serializers import ArchivableResourceSerializer, UserResourceSerializer
from apps.project.firebase import push_organization_to_firebase
from apps.tutorial.models import Tutorial
from project_types.store import get_project_property
from utils.common import clean_up_none_keys
Expand Down Expand Up @@ -348,3 +349,15 @@ class OrganizationSerializer(UserResourceSerializer[Organization], ArchivableRes
class Meta: # type: ignore[reportIncompatibleVariableOverride]
model = Organization
fields = ("name", "description", "abbreviation")

@typing.override
def create(self, validated_data: dict[str, typing.Any]) -> Organization:
organization = super().create(validated_data)
transaction.on_commit(lambda: push_organization_to_firebase.delay(organization.pk))
return organization

@typing.override
def update(self, instance: Organization, validated_data: dict[typing.Any, typing.Any]):
organization = super().update(instance, validated_data)
transaction.on_commit(lambda: push_organization_to_firebase.delay(organization.pk))
return organization
4 changes: 4 additions & 0 deletions main/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ def project_groups(project_id: str | int):
def project_tasks(project_id: str | int):
return f"/v2/groups/{project_id}/"

@staticmethod
def organization(organization_id: str | int):
return f"/v2/organisation/{organization_id}/"


# FIXME: Import utils/geo/raster_tile_server/config.py here
# FIXME: Import utils/geo/vector_tile_server/config.py here
2 changes: 1 addition & 1 deletion project_types/base/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ def push_to_firebase(self):
else:
if fb_project is None:
logger.error(
"push_to_firebase found did not find project in firebase when updating a project",
"push_to_firebase did not find project in firebase when updating a project",
extra=log_extra({"project": self.project.pk}),
)
raise InvalidProjectPushException
Expand Down
8 changes: 4 additions & 4 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,7 @@ enum Ordering {
}

"""
Organization(id, client_id, created_at, modified_at, created_by, modified_by, is_archived, archived_at, archived_by, name, description, abbreviation, unique_name)
Organization(id, client_id, created_at, modified_at, created_by, modified_by, is_archived, archived_at, archived_by, name, description, abbreviation, unique_name, firebase_push_status, firebase_last_pushed)
"""
input OrganizationCreateInput {
clientId: String!
Expand All @@ -718,7 +718,7 @@ input OrganizationCreateInput {
}

"""
Organization(id, client_id, created_at, modified_at, created_by, modified_by, is_archived, archived_at, archived_by, name, description, abbreviation, unique_name)
Organization(id, client_id, created_at, modified_at, created_by, modified_by, is_archived, archived_at, archived_by, name, description, abbreviation, unique_name, firebase_push_status, firebase_last_pushed)
"""
input OrganizationFilter {
id: IDBaseFilterLookup
Expand All @@ -741,7 +741,7 @@ type OrganizationSwipeStatsType {
}

"""
Organization(id, client_id, created_at, modified_at, created_by, modified_by, is_archived, archived_at, archived_by, name, description, abbreviation, unique_name)
Organization(id, client_id, created_at, modified_at, created_by, modified_by, is_archived, archived_at, archived_by, name, description, abbreviation, unique_name, firebase_push_status, firebase_last_pushed)
"""
type OrganizationType implements UserResourceTypeMixin {
clientId: String!
Expand Down Expand Up @@ -775,7 +775,7 @@ type OrganizationTypeOffsetPaginated {
}

"""
Organization(id, client_id, created_at, modified_at, created_by, modified_by, is_archived, archived_at, archived_by, name, description, abbreviation, unique_name)
Organization(id, client_id, created_at, modified_at, created_by, modified_by, is_archived, archived_at, archived_by, name, description, abbreviation, unique_name, firebase_push_status, firebase_last_pushed)
"""
input OrganizationUpdateInput {
clientId: String!
Expand Down
Loading