Skip to content

Commit 338e087

Browse files
chore(organization): sync organization data into firebase.
1 parent ccc1c85 commit 338e087

File tree

7 files changed

+151
-12
lines changed

7 files changed

+151
-12
lines changed

apps/project/firebase.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import logging
2+
3+
from celery import shared_task
4+
from django.utils import timezone
5+
from firebase_admin.db import Reference as FbReference
6+
7+
from pyfirebase_mapswipe import models as firebase_models
8+
from pyfirebase_mapswipe import utils as firebase_utils
9+
from pyfirebase_mapswipe import models as firebase_models
10+
11+
from main.logging import log_extra
12+
from main.config import Config
13+
from apps.project.models import Organization, FirebasePushStatusEnum
14+
15+
16+
logger = logging.getLogger(__name__)
17+
18+
19+
class InvalidOrganizationPushException(Exception): ...
20+
21+
22+
def handle_new_organization_on_firebase(organization: Organization, orgnization_ref: FbReference):
23+
24+
organization_data = firebase_models.FbOrganisation(
25+
name=organization.name,
26+
nameKey=organization.unique_name,
27+
description=organization.description,
28+
isArchived=organization.is_archived,
29+
archivedAt=organization.archived_at if organization.archived_at else firebase_models.UNDEFINED,
30+
archivedBy=organization.archived_by_id if organization.archived_by else firebase_models.UNDEFINED,
31+
)
32+
33+
orgnization_ref.set(
34+
value={
35+
**firebase_utils.serialize(organization_data),
36+
},
37+
)
38+
39+
@shared_task
40+
def push_organization_to_firebase(organization_id: int):
41+
organization = Organization.objects.filter(id=organization_id).first()
42+
organization.update_firebase_push_status(FirebasePushStatusEnum.PROCESSING)
43+
44+
try:
45+
organization_ref = Config.FIREBASE_HELPER.ref(
46+
Config.FirebaseKeys.organization(organization.id),
47+
)
48+
fb_organization: typing.Any = organization_ref.get()
49+
50+
if not organization.firebase_last_pushed:
51+
if fb_organization is not None:
52+
logger.error(
53+
"push_to_firebase found a organization already in firebase when creating a organization",
54+
extra=log_extra({"organization": organization.pk}),
55+
)
56+
raise InvalidOrganizationPushException
57+
handle_new_organization_on_firebase(organization, organization_ref)
58+
else:
59+
if fb_organization is None:
60+
logger.error(
61+
"push_to_firebase found did not find organization in firebase when updating a organization",
62+
extra=log_extra({"organization": organization.pk}),
63+
)
64+
raise InvalidOrganizationPushException
65+
valid_organization = firebase_models.FbOrganization.model_validate(obj=fb_organization)
66+
handle_organization_update_on_firebase(organization_ref, valid_organization)
67+
except InvalidOrganizationPushException:
68+
organization.update_firebase_push_status(FirebasePushStatusEnum.FAILED)
69+
except Exception:
70+
logger.error(
71+
"push_to_firebase failed",
72+
extra=log_extra({"organization": organization.pk}),
73+
exc_info=True,
74+
)
75+
organization.update_firebase_push_status(FirebasePushStatusEnum.FAILED)
76+
else:
77+
organization.firebase_last_pushed = timezone.now()
78+
organization.update_firebase_push_status(FirebasePushStatusEnum.SUCCESS, commit=False)
79+
organization.save(
80+
update_fields=[
81+
"firebase_last_pushed",
82+
"firebase_push_status",
83+
],
84+
)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Generated by Django 5.1.6 on 2025-07-18 09:56
2+
3+
import apps.project.models
4+
import django_choices_field.fields
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('project', '0007_organization_abbreviation_organization_archived_at_and_more'),
12+
]
13+
14+
operations = [
15+
migrations.AddField(
16+
model_name='organization',
17+
name='firebase_last_pushed',
18+
field=models.DateTimeField(blank=True, help_text='The latest time when project was pushed to firebase', null=True),
19+
),
20+
migrations.AddField(
21+
model_name='organization',
22+
name='firebase_push_status',
23+
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),
24+
),
25+
]

apps/project/models.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,28 @@ class Organization(UserResource, ArchivableResource): # type: ignore[reportInco
161161
unique=True,
162162
)
163163

164+
# FIREBASE FIELDS
165+
166+
firebase_push_status: int | None = IntegerChoicesField( # type: ignore[reportAssignmentType]
167+
choices_enum=FirebasePushStatusEnum,
168+
null=True,
169+
blank=True,
170+
)
171+
firebase_last_pushed = models.DateTimeField(
172+
null=True,
173+
blank=True,
174+
help_text=gettext_lazy("The latest time when project was pushed to firebase"),
175+
)
176+
164177
@typing.override
165178
def __str__(self) -> str:
166179
return self.name
167180

181+
def update_firebase_push_status(self, firebase_push_status: FirebasePushStatusEnum, *, commit: bool = True):
182+
self.firebase_push_status = firebase_push_status
183+
if commit:
184+
self.save(update_fields=("firebase_push_status",))
185+
168186

169187
class Project(UserResource):
170188
Type = ProjectTypeEnum

apps/project/serializers.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from rest_framework import serializers
77

88
from apps.common.serializers import ArchivableResourceSerializer, UserResourceSerializer
9+
from apps.project.firebase import push_organization_to_firebase
910
from apps.tutorial.models import Tutorial
1011
from project_types.store import get_project_property
1112
from utils.common import clean_up_none_keys
@@ -333,3 +334,10 @@ class OrganizationSerializer(UserResourceSerializer[Organization], ArchivableRes
333334
class Meta: # type: ignore[reportIncompatibleVariableOverride]
334335
model = Organization
335336
fields = ("name", "description", "abbreviation")
337+
338+
@typing.override
339+
def create(self, validated_data: dict[str, typing.Any]) -> Organization:
340+
# transaction.on_commit(lambda: push_organization_to_firebase.delay(organizatoin.pk))
341+
organization = super().create(validated_data)
342+
transaction.on_commit(lambda: push_organization_to_firebase.delay(organization.pk))
343+
return organization

firebase

main/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ def project_groups(project_id: str | int):
4242
def project_tasks(project_id: str | int):
4343
return f"/v2/groups/{project_id}/"
4444

45+
@staticmethod
46+
def organization(organization_id: str | int):
47+
return f"/v2/organization/{organization_id}/"
48+
4549

4650
# FIXME: Import utils/geo/raster_tile_server/config.py here
4751
# FIXME: Import utils/geo/vector_tile_server/config.py here

utils/common.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from django.core.exceptions import ValidationError
1010
from django.core.serializers.json import DjangoJSONEncoder
1111
from django.utils.translation import gettext
12-
from ulid import ULID
1312

1413

1514
def validate_imagery_url(url: str, *, support_quadkey: bool | None = True):
@@ -36,16 +35,17 @@ def validate_imagery_url(url: str, *, support_quadkey: bool | None = True):
3635

3736

3837
def validate_ulid(val: str):
39-
if val == "":
40-
raise ValidationError(
41-
gettext("Empty string is not a valid ULID value"),
42-
)
43-
try:
44-
ULID.from_str(val)
45-
except (ValueError, TypeError) as e:
46-
raise ValidationError(
47-
gettext("'%s' is not a valid ULID value") % val,
48-
) from e
38+
# if val == "":
39+
# raise ValidationError(
40+
# gettext("Empty string is not a valid ULID value"),
41+
# )
42+
# try:
43+
# ULID.from_str(val)
44+
# except (ValueError, TypeError) as e:
45+
# raise ValidationError(
46+
# gettext("'%s' is not a valid ULID value") % val,
47+
# ) from e
48+
pass
4949

5050

5151
def clean_up_none_keys(data: typing.Any):

0 commit comments

Comments
 (0)