Skip to content

Commit 94039ce

Browse files
committed
Implement Firebase Cloud Messaging support
1 parent 6cbc9a4 commit 94039ce

File tree

3 files changed

+76
-0
lines changed

3 files changed

+76
-0
lines changed

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ apscheduler==3.6.0
22
certifi==2018.11.29
33
chardet==3.0.4
44
Click==7.0
5+
firebase-admin==7.1.0
56
Flask==1.0.2
67
Flask-SQLAlchemy==2.3.2
78
Flask-Migrate==2.1.1

timeline_sync/api.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import secrets
33
import uuid
44
import requests
5+
import firebase_admin
56
from .models import db, SandboxToken, TimelinePin, UserTimeline
67
from .utils import get_uid, api_error, pin_valid
78
from .settings import config
@@ -10,6 +11,8 @@
1011

1112
api = Blueprint('api', __name__)
1213

14+
default_app = firebase_admin.initialize_app()
15+
1316

1417
def get_locker_info(user_token):
1518
if user_token is None:
@@ -28,6 +31,27 @@ def get_locker_info(user_token):
2831
beeline.add_context_field('app_uuid', locker_info['app_uuid'])
2932
return locker_info['user_id'], locker_info['app_uuid'], f"uuid:{locker_info['app_uuid']}"
3033

34+
def send_fcm_message(user_id, data):
35+
if user_id is None:
36+
raise ValueError
37+
38+
fcm_tokens = db.session.query(FcmToken).filter_by(user_id=user_id)
39+
tokens = [fcm_token.token for fcm_token in fcm_tokens]
40+
41+
message = firebase_admin.messaging.Message(
42+
data=data,
43+
tokens=tokens,
44+
)
45+
46+
response = firebase_admin.messaging.send_each_for_multicast(message)
47+
48+
if response.failure_count > 0:
49+
responses = response.responses
50+
for idx, resp in enumerate(responses):
51+
if not resp.success:
52+
FcmToken.query.filter_by(user_id=user_id, token=tokens[idx]).delete()
53+
54+
3155

3256
@api.route('/tokens/sandbox/<app_uuid>')
3357
def get_sandbox_token(app_uuid):
@@ -68,6 +92,29 @@ def sync():
6892
}
6993
return jsonify(result)
7094

95+
@api.route('/user/fcm_token/<token>')
96+
def fcm_token():
97+
user_id = get_uid()
98+
99+
if request.method == 'PUT':
100+
fcm_token_json = request.json
101+
102+
fcm_token = FcmToken.query.filter_by(user_id=user_id, token=token).one_or_none()
103+
if fcm_token is None:
104+
fcm_token = FcmToken.from_json(fcm_token_json, token, user_id)
105+
if fcm_token is None:
106+
return api_error(400)
107+
108+
db.session.add(fcm_token)
109+
db.session.commit()
110+
111+
elif request.method == 'DELETE':
112+
fcm_token = FcmToken.query.filter_by(user_id=user_id, token=token).first_or_404()
113+
fcm_token.delete()
114+
115+
db.session.commit()
116+
return 'OK'
117+
71118

72119
@api.route('/user/pins/<pin_id>', methods=['PUT', 'DELETE'])
73120
def user_pin(pin_id):
@@ -96,6 +143,8 @@ def user_pin(pin_id):
96143
db.session.add(pin)
97144
db.session.add(user_timeline)
98145
db.session.commit()
146+
147+
send_fcm_message(user_id, user_timeline.to_json())
99148
else: # update pin
100149
try:
101150
pin.update_from_json(pin_json)
@@ -111,6 +160,8 @@ def user_pin(pin_id):
111160
db.session.add(pin)
112161
db.session.add(user_timeline)
113162
db.session.commit()
163+
164+
send_fcm_message(user_id, user_timeline.to_json())
114165
except (KeyError, ValueError):
115166
beeline.add_context_field('timeline.failure.cause', 'update_pin')
116167
return api_error(400)
@@ -127,6 +178,8 @@ def user_pin(pin_id):
127178
pin=pin)
128179
db.session.add(user_timeline)
129180
db.session.commit()
181+
182+
send_fcm_message(user_id, user_timeline.to_json())
130183
return 'OK'
131184

132185

timeline_sync/models.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,28 @@ class SandboxToken(db.Model):
2020

2121
db.Index('sandbox_token_uid_appuuid_index', SandboxToken.user_id, SandboxToken.app_uuid, unique=True)
2222

23+
class FcmToken(db.Model):
24+
__tablename__ = 'fcm_tokens'
25+
token = db.Column(db.String, primary_key=True)
26+
user_id = db.Column(db.Integer)
27+
device_id = db.Column(db.String)
28+
platform = db.Column(db.String)
29+
30+
@classmethod
31+
def from_json(cls, fcm_token_json, token, user_id):
32+
try:
33+
fcm_token = cls(
34+
token=token,
35+
device_id=fcm_token_json['device_id'],
36+
platform=fcm_token_json['platform'],
37+
user_id=user_id,
38+
)
39+
return fcm_token
40+
except (KeyError, ValueError):
41+
return None
42+
43+
44+
db.Index('fcm_token_uid_token_index', FcmToken.user_id, FcmToken.token, unique=True)
2345

2446
class TimelinePin(db.Model):
2547
__tablename__ = 'timeline_pins'

0 commit comments

Comments
 (0)