Skip to content

Comments

[N3] firebase auth and update apis#5

Open
1jeanpaul1 wants to merge 8 commits intodevfrom
N3_JP
Open

[N3] firebase auth and update apis#5
1jeanpaul1 wants to merge 8 commits intodevfrom
N3_JP

Conversation

@1jeanpaul1
Copy link
Collaborator

@1jeanpaul1 1jeanpaul1 commented Dec 13, 2025

Description

  • Changes Authorization from cognito to firebase
  • Adds missing APIs
  • Adds API Docs
  • Adds Email and Push Notification flow
  • When User is in User Service, Notification are also deleted - triggered by event bridge

API Docs: https://fridgefinder.github.io/CFM_Notification/

API

Base URLs:

  • Dev: https://notifications-api-dev.communityfridgefinder.com
  • Staging: https://notifications-api-staging.communityfridgefinder.com
  • Prod: https://notifications-api-prod.communityfridgefinder.com

Authentication: All endpoints (except health check) require Firebase JWT token in Authorization: Bearer <token> header.


Endpoints

Get Notification for Specific Fridge

GET /v1/users/{user_id}/fridge-notifications/{fridge_id}

Retrieve notification preferences for a specific fridge.

Path Parameters:

  • user_id (string, required) - Firebase User ID (must match authenticated user)
  • fridge_id (string, required) - Unique fridge identifier

Success Response (200):

{
  "userId": "abc123xyz456def789",
  "fridgeId": "fridge_123abc",
  "contactTypePreferences": {
    "email": {
      "good": true,
      ...
    }
  },
  "createdAt": "2025-12-13T10:30:45.123Z",
  "updatedAt": "2025-12-13T15:20:30.456Z"
}

Create Notification Preferences

POST /v1/users/{user_id}/fridge-notifications/{fridge_id}

Create new notification preferences for a fridge.

Path Parameters:

  • user_id (string, required) - Firebase User ID (must match authenticated user)
  • fridge_id (string, required) - Unique fridge identifier

Request Body:

{
  "contactTypePreferences": {
    "email": {
      "good": true,
      ...
    },
    "device": {
      "good": false,
      ...
    }
  }
}

Success Response (201):

{
  "userId": "abc123xyz456def789",
  "fridgeId": "fridge_123abc",
  "contactTypePreferences": {
    "email": {
      "good": true,
      ...
    },
    "device": {
      "good": false,
      ...
    }
  },
  "createdAt": "2025-12-13T10:30:45.123Z",
  "updatedAt": "2025-12-13T10:30:45.123Z"
}

Update Notification Preferences

PATCH /v1/users/{user_id}/fridge-notifications/{fridge_id}

Update existing notification preferences for a fridge.

Path Parameters:

  • user_id (string, required) - Firebase User ID (must match authenticated user)
  • fridge_id (string, required) - Unique fridge identifier

Request Body:

{
  "contactTypePreferences": {
    "email": {
      "good": false,
      ...
    },
    "device": {
      "good": true,
      ...
    }
  }
}

Success Response (200):

{
  "userId": "abc123xyz456def789",
  "fridgeId": "fridge_123abc",
  "contactTypePreferences": {
    "email": {
      "good": false,
      ...
    },
    "device": {
      "good": true,
      ...
    }
  },
  "createdAt": "2025-12-13T10:30:45.123Z",
  "updatedAt": "2025-12-13T16:45:22.789Z"
}

Get All User Notifications

GET /v1/users/{user_id}/fridge-notifications

Retrieve all notification preferences for a user.

Path Parameters:

  • user_id (string, required) - Firebase User ID (must match authenticated user)

Success Response (200):

{
  "data": [
    {
      "userId": "abc123xyz456def789",
      "fridgeId": "fridge_123abc",
      "contactTypePreferences": {
        "email": {
          "good": true,
          "dirty": true,
          "outOfOrder": true,
          "notAtLocation": false,
          "ghost": false,
          "foodLevel0": true,
          "foodLevel1": false,
          "foodLevel2": false,
          "foodLevel3": false
        },
        "device": {
          "good": false,
          ...
        }
      },
      "createdAt": "2025-12-13T10:30:45.123Z",
      "updatedAt": "2025-12-13T15:20:30.456Z"
    }
  ],
  "count": 1
}

Delete Notification Preferences

DELETE /v1/users/{user_id}/fridge-notifications/{fridge_id}

Remove notification preferences for a specific fridge.

Path Parameters:

  • user_id (string, required) - Firebase User ID (must match authenticated user)
  • fridge_id (string, required) - Unique fridge identifier

Success Response (204):

No content (empty response body)

Error Responses

All errors follow a standardized format with an error object containing a code and message.

Error Response Structure

{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error message"
  }
}

Error Examples

400 Bad Request - Validation Error

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Input validation failed"
  }
}

404 Not Found

{
  "error": {
    "code": "NOT_FOUND",
    "message": "Notification preference not found"
  }
}

409 Conflict

{
  "error": {
    "code": "ALREADY_EXISTS",
    "message": "Notification preference already exists for this user and fridge"
  }
}

500 Internal Server Error

{
  "error": {
    "code": "INTERNAL_SERVER_ERROR",
    "message": "An unexpected error occurred"
  }
}

401 Unauthorized

{
  "message": "Unauthorized"
}

401 errors are returned from API Gateway if JWT token is not valid

foodLevel2: bool
foodLevel3: bool

class ContactTypePreferencesModel(BaseModel):
Copy link
Collaborator Author

@1jeanpaul1 1jeanpaul1 Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think notifications could be laid out something like this in the future on web and mobile

on web:
if user clicks on phone -> redirects them to download mobile app
if user clicks on email -> updates user preference

IMG_1912

note: The only notification that I can think of that would be email specific: Weekly/Monthly Digest which emails the user monthly stats about the fridge (not included yet)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2026-02-06 at 3 13 56 PM

Preliminary design

Authorizers:
FirebaseAuthorizer:
JwtConfiguration:
issuer: !Sub "https://securetoken.google.com/${FirebaseProjectId}"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

firebase auth

## DynamoDB Tables ##
##########################

UserFridgeNotificationsTable:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hash/PrimaryKey: userId
Range: fridgeId

@1jeanpaul1 1jeanpaul1 changed the title [N3] add firebase auth and update apis and add docs [N3] firebase auth and update apis Dec 13, 2025
Unsubscribe: {base_url}/unsubscribe?token=eyJhbGciOiJ
"""

ses.send_email(
Copy link
Collaborator Author

@1jeanpaul1 1jeanpaul1 Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2026-01-24 at 5 33 21 AM

Email Notification Preview
TODO: Add Mobile App links once launched on app stores

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: add app links
Screenshot 2026-01-25 at 2 02 01 AM

token=fcm_token,
)

response = messaging.send(message)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2026-01-24 at 3 42 39 AM

Tested on firebase web fcmtoken

TODO: confirm messagin

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API Docs: https://fridgefinder.github.io/CFM_Notification/

TODO: Review and Approve by others

email: Optional[FridgePreferencesModel] = None
device: Optional[FridgePreferencesModel] = None

class UserFridgeNotificationModel(BaseModel):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: Confirm fields and structure

Copy link

@mansoorsiddiqui mansoorsiddiqui left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good progress on the restructure. Left a few comments inline — the bigger picture stuff is around cross-service coupling and making sure we have a path to decouple the direct DB reads.

Cross-service concerns:

  • The stream processor reads directly from the user table — schema changes in the user service could silently break notifications. I know there's a note about making this an API call — just want to make sure we're tracking it.
  • Old tests were deleted but no new tests added. The new code has non-trivial logic (preference merging, condition mapping, stream processing) — are we planning to add tests in a follow-up? Would feel a lot more comfortable merging if at least the model and service layer had coverage.
  • Error codes use NOT_FOUND, ALREADY_EXISTS here vs USER_NOT_FOUND, USER_ALREADY_EXISTS in the user service. Should we align?

@@ -0,0 +1,125 @@
from xml.parsers.expat import model

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from xml.parsers.expat import model

What is this? Looks like a stray auto-import — model isn't used anywhere in the file. Should probably just remove this line.

import os
import logging
import json
import requests

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import requests

This import isn't used anywhere in the handler, and requests is also listed in requirements.txt. That's extra cold start time and package size for nothing — can we remove both?

logger.info(f"Fetching user details for userId: {user_id}")

try:
response = dynamodb.get_item(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the notification service is reading directly from the User Service's DynamoDB table here. I get why — it's simpler than making an API call — but this means we need cross-stack IAM permissions and we're coupling to the user table's schema. If the user service changes how settings or fcmToken is stored, this breaks with no compile-time or deploy-time warning. You already have the note about making this an API call. Are we OK shipping this as-is for v1 and tracking it as tech debt?

logger.setLevel(logging.INFO)

# Initialize Firebase when module is loaded
initialize_firebase()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initialize_firebase() runs at module load time. If this fails (bad credentials, network blip during cold start), the entire Lambda container is toast — every invocation will fail until AWS recycles it. Can we wrap this in a try/except, or do lazy init on first invocation?

Raises:
ClientError: If DynamoDB operation fails
"""
response = self.db_client.query(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does a single query() call without handling LastEvaluatedKey. DynamoDB caps responses at 1MB, so if a user subscribes to a ton of fridges, we'd get a truncated list with no indication anything was cut off. Are we expecting users to subscribe to enough fridges that this matters? If not, maybe just add a comment noting the limit. If yes, we need pagination.

CONDITION_MAP = {
'good': 'good',
'dirty': 'dirty',
'out of order': 'outOfOrder',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The keys here are space-separated strings coming from the status report database. This feels fragile — is there a shared contract or enum between the fridge report service and this service? If someone on the report service changes "out of order" to "outOfOrder", notifications for that condition just stop working.


if not deleted:
return error_response(404, "User Fridge Notification not found", ErrorCode.NOT_FOUND, request_id=request_id)
return success_response(204, None, request_id=request_id)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

json.dumps(None) produces the string "null", but a 204 should have no body. Might want to set body to "" or handle 204 specially in success_response.

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def get_ddb_connection() -> object:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This get_ddb_connection() function is copy-pasted in both this file and getAllUserNotifications/app.py. Can we move it into the shared layer alongside the repository and service classes?

user_id = event.get('requestContext', {}).get('authorizer', {}).get('jwt', {}).get('claims', {}).get('sub')
return user_id

def validate_request_parameters(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validate_request_parameters and the validate_input in getAllUserNotifications/app.py do essentially the same thing (check auth, check user matches JWT) with slightly different signatures. Could we extract a shared validation helper into the common layer?

push:
branches:
- main
- N3_JP

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the user service PR — N3_JP branch is still in the trigger list. Remove before merging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants