Spaces:
Running
Running
Hydra-Bolt
commited on
Commit
·
dcabd54
1
Parent(s):
a83321d
init
Browse files- .gitignore +2 -0
- Dockerfile +28 -0
- __pycache__/main.cpython-313.pyc +0 -0
- main.py +94 -0
- requirements.txt +46 -0
- server.js +43 -0
.gitignore
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
serviceAccountKey.json
|
| 2 |
+
.env
|
Dockerfile
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dockerfile for Hugging Face Spaces (FastAPI + Firebase)
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Set working directory
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# Install system dependencies
|
| 8 |
+
RUN apt-get update && apt-get install -y \
|
| 9 |
+
build-essential \
|
| 10 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 11 |
+
|
| 12 |
+
# Copy requirements
|
| 13 |
+
COPY requirements.txt ./
|
| 14 |
+
|
| 15 |
+
# Install Python dependencies
|
| 16 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 17 |
+
|
| 18 |
+
# Copy application code
|
| 19 |
+
COPY . .
|
| 20 |
+
|
| 21 |
+
# Expose port (Hugging Face Spaces expects 7860, but FastAPI default is 3000)
|
| 22 |
+
EXPOSE 7860
|
| 23 |
+
|
| 24 |
+
# Set environment variable for Hugging Face Spaces
|
| 25 |
+
ENV PORT=7860
|
| 26 |
+
|
| 27 |
+
# Start FastAPI server on the expected port
|
| 28 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
__pycache__/main.cpython-313.pyc
ADDED
|
Binary file (4.47 kB). View file
|
|
|
main.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, HTTPException
|
| 2 |
+
from pydantic import BaseModel
|
| 3 |
+
from typing import List, Optional, Dict, Any
|
| 4 |
+
import firebase_admin
|
| 5 |
+
from firebase_admin import credentials, messaging
|
| 6 |
+
import json
|
| 7 |
+
import os
|
| 8 |
+
from dotenv import load_dotenv
|
| 9 |
+
|
| 10 |
+
# Load environment variables from .env file
|
| 11 |
+
load_dotenv()
|
| 12 |
+
# Load Firebase service account key
|
| 13 |
+
service_account_json = os.environ.get("FIREBASE_SERVICE_ACCOUNT_JSON")
|
| 14 |
+
if not service_account_json:
|
| 15 |
+
raise EnvironmentError("FIREBASE_SERVICE_ACCOUNT_JSON environment variable not set")
|
| 16 |
+
|
| 17 |
+
service_account = json.loads(service_account_json)
|
| 18 |
+
|
| 19 |
+
# Initialize Firebase Admin SDK
|
| 20 |
+
cred = credentials.Certificate(service_account)
|
| 21 |
+
firebase_admin.initialize_app(cred)
|
| 22 |
+
|
| 23 |
+
# Create FastAPI app
|
| 24 |
+
app = FastAPI(
|
| 25 |
+
title="FCM Notification Server",
|
| 26 |
+
description="A FastAPI server for sending Firebase Cloud Messaging notifications",
|
| 27 |
+
version="1.0.0"
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
# Pydantic models for request/response
|
| 31 |
+
class NotificationRequest(BaseModel):
|
| 32 |
+
tokens: List[str]
|
| 33 |
+
title: str
|
| 34 |
+
body: str
|
| 35 |
+
data: Optional[Dict[str, Any]] = {}
|
| 36 |
+
|
| 37 |
+
class NotificationResponse(BaseModel):
|
| 38 |
+
success: bool
|
| 39 |
+
response: Optional[Dict[str, Any]] = None
|
| 40 |
+
error: Optional[str] = None
|
| 41 |
+
|
| 42 |
+
@app.get("/")
|
| 43 |
+
async def root():
|
| 44 |
+
"""Root endpoint to check if the server is running"""
|
| 45 |
+
return {"message": "🚀 FCM FastAPI Server is running!"}
|
| 46 |
+
|
| 47 |
+
@app.post("/send", response_model=NotificationResponse)
|
| 48 |
+
async def send_notification(request: NotificationRequest):
|
| 49 |
+
"""Send notification to multiple tokens"""
|
| 50 |
+
try:
|
| 51 |
+
if not request.tokens or len(request.tokens) == 0:
|
| 52 |
+
raise HTTPException(status_code=400, detail="No tokens provided")
|
| 53 |
+
|
| 54 |
+
# Create the message
|
| 55 |
+
message = messaging.MulticastMessage(
|
| 56 |
+
notification=messaging.Notification(
|
| 57 |
+
title=request.title,
|
| 58 |
+
body=request.body
|
| 59 |
+
),
|
| 60 |
+
data=request.data or {},
|
| 61 |
+
tokens=request.tokens
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
# Send the message
|
| 65 |
+
response = messaging.send_each_for_multicast(message)
|
| 66 |
+
|
| 67 |
+
return NotificationResponse(
|
| 68 |
+
success=True,
|
| 69 |
+
response={
|
| 70 |
+
"success_count": response.success_count,
|
| 71 |
+
"failure_count": response.failure_count,
|
| 72 |
+
"responses": [
|
| 73 |
+
{
|
| 74 |
+
"success": resp.success,
|
| 75 |
+
"message_id": resp.message_id if resp.success else None,
|
| 76 |
+
"exception": str(resp.exception) if resp.exception else None
|
| 77 |
+
}
|
| 78 |
+
for resp in response.responses
|
| 79 |
+
]
|
| 80 |
+
}
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
except Exception as error:
|
| 84 |
+
print(f"Error sending notification: {error}")
|
| 85 |
+
raise HTTPException(status_code=500, detail=str(error))
|
| 86 |
+
|
| 87 |
+
@app.get("/health")
|
| 88 |
+
async def health_check():
|
| 89 |
+
"""Health check endpoint"""
|
| 90 |
+
return {"status": "healthy", "service": "FCM Notification Server"}
|
| 91 |
+
|
| 92 |
+
if __name__ == "__main__":
|
| 93 |
+
import uvicorn
|
| 94 |
+
uvicorn.run(app, host="0.0.0.0", port=3000)
|
requirements.txt
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
annotated-types==0.7.0
|
| 2 |
+
anyio==4.10.0
|
| 3 |
+
CacheControl==0.14.3
|
| 4 |
+
cachetools==5.5.2
|
| 5 |
+
certifi==2025.8.3
|
| 6 |
+
cffi==2.0.0
|
| 7 |
+
charset-normalizer==3.4.3
|
| 8 |
+
click==8.3.0
|
| 9 |
+
cryptography==46.0.1
|
| 10 |
+
fastapi==0.117.1
|
| 11 |
+
firebase_admin==7.1.0
|
| 12 |
+
google-api-core==2.25.1
|
| 13 |
+
google-auth==2.40.3
|
| 14 |
+
google-cloud-core==2.4.3
|
| 15 |
+
google-cloud-firestore==2.21.0
|
| 16 |
+
google-cloud-storage==3.4.0
|
| 17 |
+
google-crc32c==1.7.1
|
| 18 |
+
google-resumable-media==2.7.2
|
| 19 |
+
googleapis-common-protos==1.70.0
|
| 20 |
+
grpcio==1.75.0
|
| 21 |
+
grpcio-status==1.75.0
|
| 22 |
+
h11==0.16.0
|
| 23 |
+
h2==4.3.0
|
| 24 |
+
hpack==4.1.0
|
| 25 |
+
httpcore==1.0.9
|
| 26 |
+
httpx==0.28.1
|
| 27 |
+
hyperframe==6.1.0
|
| 28 |
+
idna==3.10
|
| 29 |
+
msgpack==1.1.1
|
| 30 |
+
proto-plus==1.26.1
|
| 31 |
+
protobuf==6.32.1
|
| 32 |
+
pyasn1==0.6.1
|
| 33 |
+
pyasn1_modules==0.4.2
|
| 34 |
+
pycparser==2.23
|
| 35 |
+
pydantic==2.11.9
|
| 36 |
+
pydantic_core==2.33.2
|
| 37 |
+
PyJWT==2.10.1
|
| 38 |
+
python-dotenv==1.1.1
|
| 39 |
+
requests==2.32.5
|
| 40 |
+
rsa==4.9.1
|
| 41 |
+
sniffio==1.3.1
|
| 42 |
+
starlette==0.48.0
|
| 43 |
+
typing-inspection==0.4.1
|
| 44 |
+
typing_extensions==4.15.0
|
| 45 |
+
urllib3==2.5.0
|
| 46 |
+
uvicorn==0.36.0
|
server.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import express from "express";
|
| 2 |
+
import bodyParser from "body-parser";
|
| 3 |
+
import admin from "firebase-admin";
|
| 4 |
+
import fs from "fs";
|
| 5 |
+
|
| 6 |
+
// Load Firebase service account key
|
| 7 |
+
const serviceAccount = JSON.parse(
|
| 8 |
+
fs.readFileSync("./serviceAccountKey.json", "utf-8")
|
| 9 |
+
);
|
| 10 |
+
|
| 11 |
+
admin.initializeApp({
|
| 12 |
+
credential: admin.credential.cert(serviceAccount),
|
| 13 |
+
});
|
| 14 |
+
|
| 15 |
+
const app = express();
|
| 16 |
+
app.use(bodyParser.json());
|
| 17 |
+
|
| 18 |
+
// Send notification to multiple tokens
|
| 19 |
+
app.post("/send", async (req, res) => {
|
| 20 |
+
try {
|
| 21 |
+
const { tokens, title, body, data } = req.body;
|
| 22 |
+
|
| 23 |
+
if (!tokens || tokens.length === 0) {
|
| 24 |
+
return res.status(400).json({ error: "No tokens provided" });
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
const message = {
|
| 28 |
+
notification: { title, body },
|
| 29 |
+
data: data || {}, // custom key-value data
|
| 30 |
+
tokens,
|
| 31 |
+
};
|
| 32 |
+
|
| 33 |
+
const response = await admin.messaging().sendEachForMulticast(message);
|
| 34 |
+
res.json({ success: true, response });
|
| 35 |
+
} catch (error) {
|
| 36 |
+
console.error("Error sending notification:", error);
|
| 37 |
+
res.status(500).json({ error: error.message });
|
| 38 |
+
}
|
| 39 |
+
});
|
| 40 |
+
|
| 41 |
+
app.listen(3000, () => {
|
| 42 |
+
console.log("🚀 FCM Server running on http://localhost:3000");
|
| 43 |
+
});
|