8000 Responding to comments from Jerjou, removed some abbreviations, reverted config file by mogar1980 · Pull Request #590 · GoogleCloudPlatform/python-docs-samples · GitHub
[go: up one dir, main page]

Skip to content

Responding to comments from Jerjou, removed some abbreviations, reverted config file #590

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 36 additions & 13 deletions appengine/standard/firebase/firetactoe/firetactoe.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,19 @@


def _get_firebase_db_url(_memo={}):
"""Grabs the databaseURL from the Firebase config snippet."""
# Memoize the value, to avoid parsing the code snippet every time
"""Grabs the databaseURL from the Firebase config snippet. Regex looks
scary, but all it is doing is pulling the 'databaseURL' field from the
Firebase javascript snippet"""
if 'dburl' not in _memo:
# Memoize the value, to avoid parsing the code snippet every time
regex = re.compile(r'\bdatabaseURL\b.*?["\']([^"\']+)')
cwd = os.path.dirname(__file__)
with open(os.path.join(cwd, 'templates', _FIREBASE_CONFIG)) as f:
url = next(regex.search(line) for line in f if regex.search(line))
_memo['dburl'] = url.group(1)
return _memo['dburl']


# [START authed_http]
def _get_http(_memo={}):
"""Provides an authed http object."""
if 'http' not in _memo:
Expand All @@ -75,17 +77,24 @@ def _get_http(_memo={}):
creds.authorize(http)
_memo['http'] = http
return _memo['http']
# [END authed_http]


# [START send_msg]
def _send_firebase_message(u_id, message=None):
"""Updates data in firebase. If a message is provided, then it updates
the data at /channels/<channel_id> with the message using the PATCH
http method. If no message is provided, then the data at this location
is deleted using the DELETE http method
"""
url = '{}/channels/{}.json'.format(_get_firebase_db_url(), u_id)

if message:
return _get_http().request(url, 'PATCH', body=message)
else:
return _get_http().request(url, 'DELETE')
# [END send_msg]


# [START create_token]
def create_custom_token(uid, valid_minutes=60):
"""Create a secure token for the given id.

Expand All @@ -94,25 +103,29 @@ def create_custom_token(uid, valid_minutes=60):
security rules to prevent unauthorized access. In this case, the uid will
be the channel id which is a combination of user_id and game_key
"""
header = base64.b64encode(json.dumps({'typ': 'JWT', 'alg': 'RS256'}))

# use the app_identity service from google.appengine.api to get the
# project's service account email automatically
client_email = app_identity.get_service_account_name()

now = int(time.time())
# encode the required claims
# per https://firebase.google.com/docs/auth/server/create-custom-tokens
payload = base64.b64encode(json.dumps({
'iss': client_email,
'sub': client_email,
'aud': _IDENTITY_ENDPOINT,
'uid': uid,
'uid': uid, # this is the important parameter as it will be the channel id
'iat': now,
'exp': now + (valid_minutes * 60),
}))

# add standard header to identify this as a JWT
header = base64.b64encode(json.dumps({'typ': 'JWT', 'alg': 'RS256'}))
to_sign = '{}.{}'.format(header, payload)

# Sign the jwt
# Sign the jwt using the built in app_identity service
return '{}.{}'.format(to_sign, base64.b64encode(
app_identity.sign_blob(to_sign)[1]))

# [END create_token]

class Game(ndb.Model):
"""All the data we store for a game"""
Expand All @@ -128,6 +141,7 @@ def to_json(self):
d['winningBoard'] = d.pop('winning_board')
return json.dumps(d, default=lambda user: user.user_id())

# [START send_update]
def send_update(self):
"""Updates Firebase's copy of the board."""
message = self.to_json()
Expand All @@ -140,6 +154,7 @@ def send_update(self):
_send_firebase_message(
self.userO.user_id() + self.key.id(),
message=message)
# [END send_update]

def _check_win(self):
if self.moveX:
Expand All @@ -161,6 +176,7 @@ def _check_win(self):
if ' ' not in self.board:
self.winner = 'Noone'

# [START make_move]
def make_move(self, position, user):
# If the user is a player, and it's their move
if (user in (self.userX, self.userO)) and (
Expand All @@ -175,8 +191,9 @@ def make_move(self, position, user):
self.put()
self.send_update()
return
# [END make_move]


# [START move_route]
@app.route('/move', methods=['POST'])
def move():
game = Game.get_by_id(request.args.get('g'))
Expand All @@ -185,8 +202,9 @@ def move():
return 'Game not found, or invalid position', 400
game.make_move(position, users.get_current_user())
return ''
# [END move_route]


# [START route_delete]
@app.route('/delete', methods=['POST'])
def delete():
game = Game.get_by_id(request.args.get('g'))
Expand All @@ -196,6 +214,7 @@ def delete():
_send_firebase_message(
user.user_id() + game.key.id(), message=None)
return ''
# [END route_delete]


@app.route('/opened', methods=['POST'])
Expand Down Expand Up @@ -226,6 +245,7 @@ def main_page():
game.userO = user
game.put()

# [START pass_token]
# choose a unique identifier for channel_id
channel_id = user.user_id() + game_key
# encrypt the channel_id and send it as a custom token to the
Expand All @@ -236,6 +256,8 @@ def main_page():
_send_firebase_message(
channel_id, message=game.to_json())

# game_link is a url that you can open in another browser to play
# against this player
game_link = '{}?g={}'.format(request.base_url, game_key)

# push all the data to the html template so the client will
Expand All @@ -250,3 +272,4 @@ def main_page():
}

return flask.render_template('fire_index.html', **template_values)
# [END pass_token]
108 changes: 108 additions & 0 deletions appengine/standard/firebase/firetactoe/rest_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Demonstration of the Firebase REST API in Python"""

# [START rest_writing_data]
import base64
import json
import os
import re
import time
import urllib


from flask import request
import httplib2
from oauth2client.client import GoogleCredentials


_FIREBASE_SCOPES = [
'https://www.googleapis.com/auth/firebase.database',
'https://www.googleapis.com/auth/userinfo.email']


def _get_http(_memo={}):
"""Provides an authed http object."""
if 'http' not in _memo:
# Memoize the authorized http, to avoid fetching new access tokens
http = httplib2.Http()
# Use application default credentials to make the Firebase calls
# https://firebase.google.com/docs/reference/rest/database/user-auth
creds = GoogleCredentials.get_application_default().create_scoped(
_FIREBASE_SCOPES)
creds.authorize(http)
_memo['http'] = http
return _memo['http']

def firebase_put(path, value=None):
"""Writes data to Firebase. Value should be a valid json object.
Put writes an entire object at the given database path. Updates to
fields cannot be performed without overwriting the entire object
"""
response, content = _get_http().request(path, method='PUT', body=value)
if content != "null":
return json.loads(content)
else:
return None

def firebase_patch(path, value=None):
"""Allows specific children or fields to be updated without overwriting
the entire object. Value should again be a valid json object
"""
response, content = _get_http().request(path, method='PATCH', body=value)
if content != "null":
return json.loads(content)
else:
return None

def firebase_post(path, value=None):
"""Post allows an object to be added to an existing list of data.
Value should once again be a valid json object. A successful request
will be indicated by a 200 OK HTTP status code. The response will
contain a new attribute "name" which is the key for the child added
"""
response, content = _get_http().request(path, method='POST', body=value)
if content != "null":
return json.loads(content)
else:
return None

# [END rest_writing_data]
# [START rest_reading_data]
def firebase_get(path):
"""Get request allows reading of data at a particular path
A successful request will be indicated by a 200 OK HTTP status code.
The response will contain the data being retrieved
"""
response, content = _get_http().request(path, method='GET')
if content != "null":
return json.loads(content)
else:
return None
# [END rest_reading_data]
# [START rest_deleting_data]

def firebase_delete(path):
"""Delete removes the data at a particular path
A successful request will be indicated by a 200 OK HTTP status code
with a response containing JSON null.
"""
response, content = _get_http().request(path, method='DELETE')
if content != "null":
return json.loads(content)
else:
return None

# [END rest_deleting_data]
10 changes: 10 additions & 0 deletions appengine/standard/firebase/firetactoe/static/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ function initGame(gameKey, me, token, channelId, initialMessage) {
return state.userX === state.me ? 'X' : 'O';
}

// [START move_in_square]
/**
* Send the user's latest move back to the server
*/
Expand All @@ -92,6 +93,7 @@ function initGame(gameKey, me, token, channelId, initialMessage) {
$.post('/move', {i: id});
}
}
// [END move_in_square]

/**
* This method lets the server know that the user has opened the channel
Expand All @@ -109,6 +111,7 @@ function initGame(gameKey, me, token, channelId, initialMessage) {
$.post('/delete');
}

// [START remove_listener]
/**
* This method is called every time an event is fired from Firebase
* it updates the entire game state and checks for a winner
Expand All @@ -124,29 +127,36 @@ function initGame(gameKey, me, token, channelId, initialMessage) {
deleteChannel(); //delete the data we wrote
}
}
// [END remove_listener]

// [START open_channel]
/**
* This function opens a realtime communication channel with Firebase
* It logs in securely using the client token passed from the server
* then it sets up a listener on the proper database path (also passed by server)
* finally, it calls onOpened() to let the server know it is ready to receive messages
*/
function openChannel() {
// [START auth_login]
// sign into Firebase with the token passed from the server
firebase.auth().signInWithCustomToken(token).catch(function(error) {
console.log('Login Failed!', error.code);
console.log('Error message: ', error.message);
});
// [END auth_login]

// [START add_listener]
// setup a database reference at path /channels/channelId
channel = firebase.database().ref('channels/' + channelId);
// add a listener to the path that fires any time the value of the data changes
channel.on('value', function(data) {
onMessage(data.val());
});
// [END add_listener]
onOpened();
// let the server know that the channel is open
}
// [END open_channel]

/**
* This function opens a communication channel with the server
Expand Down
0