Description
I am trying to implement a notification features which notify the user every selected day of a week at a specific selected time. I use celery, redis, firebase for this task.
My backend (Django):
pms_backend (main app or the default one):
settings.py:
CELERY_BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
celery.py (same directory as settings.py) :
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pms_backend.settings')
app = Celery('pms_backend')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
internal_services (another app):
tasks.py:
c```
red = credentials.Certificate(os.path.join(settings.BASE_DIR, 'firebase.json'))
firebase_admin.initialize_app(cred)
@shared_task
def send_push_notification(title, body, user_tokens):
message = messaging.MulticastMessage(
notification=messaging.Notification(
title=title,
body=body
),
tokens=user_tokens
)
try:
response = messaging.send_multicast(message)
print('Successfully sent message:', response)
except Exception as e:
print('Error sending message:', e)
views.py:
class ProductivityModeView(generics.CreateAPIView):
serializer_class = Productivity
permission_classes = [IsAuthenticated]
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
productivity = serializer.save()
ProductivityModeUser.objects.create(
user_id=request.user,
productivity_id=productivity
)
user_tokens = [user.firebase_token for user in User.objects.filter(id=request.user.id) if user.firebase_token]
for day in productivity.selected_day:
schedule_task(
f'productivity_start_{request.user.id}_{day}_{productivity.start_time}',
day_of_week=day,
task_time=time(hour=productivity.start_time.hour, minute=productivity.start_time.minute),
args=['Productivity Mode Started', f'Your productivity mode has started at {productivity.start_time}', user_tokens]
)
schedule_task(
f'productivity_end_{request.user.id}_{day}_{productivity.start_time}',
day_of_week=day,
task_time=time(hour=productivity.end_time.hour, minute=productivity.end_time.minute),
args=['Productivity Mode Ended', f'Your productivity mode has ended at {productivity.end_time}', user_tokens]
)
return Response({"message": "Productivity mode created successfully"}, status=status.HTTP_201_CREATED)
else:
print("Serializer errors:", serializer.errors)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
My Frontend (React):
public/firebase-messaging-sw.js
importScripts('https://www.gstatic.com/firebasejs/10.1.0/firebase-app-compat.js')
importScripts('https://www.gstatic.com/firebasejs/10.1.0/firebase-messaging-compat.js')
firebase.initializeApp({
apiKey: "AIzaSyD6na5iVJPYZdFGRB79iKIwMdpBKxDLowg",
authDomain: "aqueous-thought-426115-i6.firebaseapp.com",
projectId: "aqueous-thought-426115-i6",
storageBucket: "aqueous-thought-426115-i6.appspot.com",
messagingSenderId: "505424754285",
appId: "1:505424754285:web:d99417903cef886f36b164",
measurementId: "G-W4LKDCL06Y"
});
const messaging = firebase.messaging();
messaging.onBackgroundMessage((payload) => {
console.log('Received background message ', payload);
const notificationTitle = payload.notification.title;
const notificationOptions = {
body: payload.notification.body,
};
self.registration.showNotification(notificationTitle, notificationOptions);
});
components/Productivity/
`
const ProductivityModeForm = () => {
const vapidKey = 'BPQqDxdT7jRvEXZFNvgTNn355V3hLx4OAX78s7aDiiWO6RGzyxkBv5vD4eEMzOYK6Btywn6uOQyQ3Pkbequfi90';
const [productivityConfig, setProductivityConfig] = useState({
start_time: '',
end_time: '',
selected_day: []
});
const handleChange = (event) => {
const { name, value, type, checked } = event.target;
setProductivityConfig(prevConfig => ({
...prevConfig,
[name]: type === 'checkbox' ? checked : value
}));
};
const handleSubmit = async (event) => {
event.preventDefault();
try {
console.log(productivityConfig);
const res = await api.post(PRODUCTIVITY, productivityConfig, {
withCredentials: true,
headers: {
'X-CSRFToken': getCsrfToken()
}
});
console.log(res);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
const requestPermissionAndFetchToken = async () => {
try {
console.log('Requesting notification permission...');
const permission = await Notification.requestPermission();
if (permission === 'granted') {
console.log('Notification permission granted.');
const registration = await navigator.serviceWorker.register('/firebase-messaging-sw.js');
const token = await getToken(messaging, { vapidKey, serviceWorkerRegistration: registration });
console.log('Firebase token:', token);
const data = { firebase_token: token };
console.log(TOKEN_URL)
const res = await api.post(TOKEN_URL, data, {
withCredentials: true,
headers: {
'X-CSRFToken': getCsrfToken()
}
});
console.log('Token response:', res);
} else {
console.error('Notification permission not granted');
}
} catch (error) {
console.error('Error getting Firebase token:', error);
}
};
requestPermissionAndFetchToken();
}, []);
useEffect(() => {
console.log('Subscribing to messages...');
const unsubscribe = onMessage(messaging, (payload) => {
console.log('Message received:', payload);
const notificationTitle = payload.notification.title;
const notificationOptions = {
body: payload.notification.body,
icon: 'logo192.png',
};
new Notification(notificationTitle, notificationOptions);
});
return unsubscribe;
}, []);
return (
<form onSubmit={handleSubmit}>
<h2>Productivity Mode</h2>
<label>
Start Time:
<input type="time" name="start_time" value={productivityConfig.start_time} onChange={handleChange}/>
</label>
<br />
<label>
End Time:
<input type="time" name="end_time" value={productivityConfig.end_time} onChange={handleChange}/>
</label>
<br />
<label>
Select Day(s) of the Week:
<select multiple name="selected_day" value={productivityConfig.selected_day} onChange={handleChange}>
<option value={1}>Monday</option>
<option value={2}>Tuesday</option>
<option value={3}>Wednesday</option>
<option value={4}>Thursday</option>
<option value={5}>Friday</option>
<option value={6}>Saturday</option>
<option value={7}>Sunday</option>
</select>
</label>
<br />
<button type="submit">Submit</button>
</form>
);
};
export default ProductivityModeForm;
When I create a productivity instance, everything is fine and the fcm token are also stored to user table in the database. Celery worker and beat didn't output any error.
However, I never got notification.
The logs of celery worker :
celery -A pms_backend worker -l info
-------------- [email protected] v5.3.6 (emerald-rush)
--- ***** -----
-- ******* ---- macOS-10.16-x86_64-i386-64bit 2024-06-19 16:37:23
- *** --- * ---
- ** ---------- [config]
- ** ---------- .> app: pms_backend:0x7f9cc87063d0
- ** ---------- .> transport: redis://localhost:6379/0
- ** ---------- .> results: redis://localhost:6379/0
- *** --- * --- .> concurrency: 8 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery
[tasks]
. internal_services.tasks.send_push_notification
[2024-06-19 16:37:24,916: INFO/MainProcess] Connected to redis://localhost:6379/0
[2024-06-19 16:37:24,916: WARNING/MainProcess]
[2024-06-19 16:37:24,919: INFO/MainProcess] mingle: searching for neighbors
[2024-06-19 16:37:25,944: INFO/MainProcess] mingle: all alone
[2024-06-19 16:37:25,971: INFO/MainProcess] [email protected] ready.
The logs of celery beat :
celery -A pms_backend beat -l info
celery beat v5.3.6 (emerald-rush) is starting.
__ - ... __ - _
LocalTime -> 2024-06-19 16:37:37
Configuration ->
. broker -> redis://localhost:6379/0
. loader -> celery.loaders.app.AppLoader
. scheduler -> celery.beat.PersistentScheduler
. db -> celerybeat-schedule
. logfile -> [stderr]@%INFO
. maxinterval -> 5.00 minutes (300s)
[2024-06-19 16:37:37,614: INFO/MainProcess] beat: Starting...