diff --git a/appengine/angular/README.md b/appengine/angular/README.md new file mode 100644 index 000000000000..22dab2437490 --- /dev/null +++ b/appengine/angular/README.md @@ -0,0 +1,43 @@ +## App Engine AngularJS "Hello World" Python + +A simple [AngularJS](http://angularjs.org/) CRUD application +for [Google App Engine](https://appengine.google.com/). + +Author: Fred Sauer + + +## Project setup + +1. Install the [App Engine Python SDK](https://developers.google.com/appengine/downloads) + + +## Testing the app locally + +To run the app locally: + +``` +dev_appserver.py . +``` + + +## Deploying + +To deploy the application: + +1. Use the [Google Cloud Console](https://cloud.google.com/console) to create a project +1. Replace `your-app-id` in `app.yaml` with the project id from the previous step +1. Deploy the application: + +``` +appcfg.py --oauth2 update . +``` + + +## Contributing changes + +See [CONTRIB.md](CONTRIB.md) + + +# Licensing + +See [LICENSE](LICENSE) diff --git a/appengine/angular/app.yaml b/appengine/angular/app.yaml new file mode 100644 index 000000000000..56ae94266f9a --- /dev/null +++ b/appengine/angular/app.yaml @@ -0,0 +1,21 @@ +application: your-app-id +version: 1 +runtime: python27 +threadsafe: true +api_version: 1 + +handlers: +- url: /favicon\.ico + static_files: favicon.ico + upload: favicon\.ico + +- url: /rest/.* + script: main.APP + +- url: (.*)/ + static_files: app\1/index.html + upload: app + +- url: (.*) + static_files: app\1 + upload: app diff --git a/appengine/angular/app/css/app.css b/appengine/angular/app/css/app.css new file mode 100644 index 000000000000..fdcbacb1c12c --- /dev/null +++ b/appengine/angular/app/css/app.css @@ -0,0 +1,5 @@ +.status { + color: blue; + padding: 1em; + height: 1em; +} diff --git a/appengine/angular/app/index.html b/appengine/angular/app/index.html new file mode 100644 index 000000000000..abb8b9058852 --- /dev/null +++ b/appengine/angular/app/index.html @@ -0,0 +1,14 @@ + + + + + + + + + +

AngularJS Guest List

+

+    
+ + diff --git a/appengine/angular/app/js/app.js b/appengine/angular/app/js/app.js new file mode 100644 index 000000000000..2db33db4c215 --- /dev/null +++ b/appengine/angular/app/js/app.js @@ -0,0 +1,123 @@ +'use strict'; + +var App = angular.module('App', ['ngRoute']); + +App.factory('myHttpInterceptor', function($rootScope, $q) { + return { + 'requestError': function(config) { + $rootScope.status = 'HTTP REQUEST ERROR ' + config; + return config || $q.when(config); + }, + 'responseError': function(rejection) { + $rootScope.status = 'HTTP RESPONSE ERROR ' + rejection.status + '\n' + + rejection.data; + return $q.reject(rejection); + }, + }; +}); + +App.factory('guestService', function($rootScope, $http, $q, $log) { + $rootScope.status = 'Retrieving data...'; + var deferred = $q.defer(); + $http.get('rest/query') + .success(function(data, status, headers, config) { + $rootScope.guests = data; + deferred.resolve(); + $rootScope.status = ''; + }); + return deferred.promise; +}); + +App.config(function($routeProvider) { + $routeProvider.when('/', { + controller : 'MainCtrl', + templateUrl: '/partials/main.html', + resolve : { 'guestService': 'guestService' }, + }); + $routeProvider.when('/invite', { + controller : 'InsertCtrl', + templateUrl: '/partials/insert.html', + }); + $routeProvider.when('/update/:id', { + controller : 'UpdateCtrl', + templateUrl: '/partials/update.html', + resolve : { 'guestService': 'guestService' }, + }); + $routeProvider.otherwise({ + redirectTo : '/' + }); +}); + +App.config(function($httpProvider) { + $httpProvider.interceptors.push('myHttpInterceptor'); +}); + +App.controller('MainCtrl', function($scope, $rootScope, $log, $http, $routeParams, $location, $route) { + + $scope.invite = function() { + $location.path('/invite'); + }; + + $scope.update = function(guest) { + $location.path('/update/' + guest.id); + }; + + $scope.delete = function(guest) { + $rootScope.status = 'Deleting guest ' + guest.id + '...'; + $http.post('/rest/delete', {'id': guest.id}) + .success(function(data, status, headers, config) { + for (var i=0; i<$rootScope.guests.length; i++) { + if ($rootScope.guests[i].id == guest.id) { + $rootScope.guests.splice(i, 1); + break; + } + } + $rootScope.status = ''; + }); + }; + +}); + +App.controller('InsertCtrl', function($scope, $rootScope, $log, $http, $routeParams, $location, $route) { + + $scope.submitInsert = function() { + var guest = { + first : $scope.first, + last : $scope.last, + }; + $rootScope.status = 'Creating...'; + $http.post('/rest/insert', guest) + .success(function(data, status, headers, config) { + $rootScope.guests.push(data); + $rootScope.status = ''; + }); + $location.path('/'); + } +}); + +App.controller('UpdateCtrl', function($routeParams, $rootScope, $scope, $log, $http, $location) { + + for (var i=0; i<$rootScope.guests.length; i++) { + if ($rootScope.guests[i].id == $routeParams.id) { + $scope.guest = angular.copy($rootScope.guests[i]); + } + } + + $scope.submitUpdate = function() { + $rootScope.status = 'Updating...'; + $http.post('/rest/update', $scope.guest) + .success(function(data, status, headers, config) { + for (var i=0; i<$rootScope.guests.length; i++) { + if ($rootScope.guests[i].id == $scope.guest.id) { + $rootScope.guests.splice(i,1); + break; + } + } + $rootScope.guests.push(data); + $rootScope.status = ''; + }); + $location.path('/'); + }; + +}); + diff --git a/appengine/angular/app/partials/insert.html b/appengine/angular/app/partials/insert.html new file mode 100644 index 000000000000..b4ed4c6c965b --- /dev/null +++ b/appengine/angular/app/partials/insert.html @@ -0,0 +1,12 @@ +

Invite another guest

+
+

+ + +

+

+ + +

+ +
diff --git a/appengine/angular/app/partials/main.html b/appengine/angular/app/partials/main.html new file mode 100644 index 000000000000..1d00341acc86 --- /dev/null +++ b/appengine/angular/app/partials/main.html @@ -0,0 +1,7 @@ +

Guest list

+ +
+ + + {{ $index + 1 }}. {{ guest.first }} {{ guest.last }} +
diff --git a/appengine/angular/app/partials/update.html b/appengine/angular/app/partials/update.html new file mode 100644 index 000000000000..02711774231b --- /dev/null +++ b/appengine/angular/app/partials/update.html @@ -0,0 +1,16 @@ +

Update guest information

+
+

+ + +

+

+ + +

+

+ + +

+ +
diff --git a/appengine/angular/main.py b/appengine/angular/main.py new file mode 100644 index 000000000000..3380d304a1ae --- /dev/null +++ b/appengine/angular/main.py @@ -0,0 +1,75 @@ +# Copyright 2013 Google, Inc +# +# 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. + +import json + +import model + +import webapp2 + + +def AsDict(guest): + return {'id': guest.key.id(), 'first': guest.first, 'last': guest.last} + + +class RestHandler(webapp2.RequestHandler): + + def dispatch(self): + # time.sleep(1) + super(RestHandler, self).dispatch() + + def SendJson(self, r): + self.response.headers['content-type'] = 'text/plain' + self.response.write(json.dumps(r)) + + +class QueryHandler(RestHandler): + + def get(self): + guests = model.AllGuests() + r = [AsDict(guest) for guest in guests] + self.SendJson(r) + + +class UpdateHandler(RestHandler): + + def post(self): + r = json.loads(self.request.body) + guest = model.UpdateGuest(r['id'], r['first'], r['last']) + r = AsDict(guest) + self.SendJson(r) + + +class InsertHandler(RestHandler): + + def post(self): + r = json.loads(self.request.body) + guest = model.InsertGuest(r['first'], r['last']) + r = AsDict(guest) + self.SendJson(r) + + +class DeleteHandler(RestHandler): + + def post(self): + r = json.loads(self.request.body) + model.DeleteGuest(r['id']) + + +APP = webapp2.WSGIApplication([ + ('/rest/query', QueryHandler), + ('/rest/insert', InsertHandler), + ('/rest/delete', DeleteHandler), + ('/rest/update', UpdateHandler), +], debug=True) diff --git a/appengine/angular/model.py b/appengine/angular/model.py new file mode 100644 index 000000000000..46d1b5aee12a --- /dev/null +++ b/appengine/angular/model.py @@ -0,0 +1,41 @@ +# Copyright 2013 Google, Inc +# +# 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. + +from google.appengine.ext import ndb + + +class Guest(ndb.Model): + first = ndb.StringProperty() + last = ndb.StringProperty() + + +def AllGuests(): + return Guest.query() + + +def UpdateGuest(id, first, last): + guest = Guest(id=id, first=first, last=last) + guest.put() + return guest + + +def InsertGuest(first, last): + guest = Guest(first=first, last=last) + guest.put() + return guest + + +def DeleteGuest(id): + key = ndb.Key(Guest, id) + key.delete() diff --git a/appengine/angular/scripts/deploy.sh b/appengine/angular/scripts/deploy.sh new file mode 100755 index 000000000000..935982932949 --- /dev/null +++ b/appengine/angular/scripts/deploy.sh @@ -0,0 +1,72 @@ +#!/bin/bash +# +set -ue + +VERSION=$(git log -1 --pretty=format:%H) +if [ -n "$(git status --porcelain)" ] +then + VERSION="dirty-$VERSION" +fi + +git status +echo +echo -e "Hit [ENTER] to continue: \c" +read + +SCRIPTS_DIR=$( dirname $0 ) +ROOT_DIR=$( dirname $SCRIPTS_DIR ) + +APPCFG=$(which appcfg.py) \ + || (echo "ERROR: appcfg.py must be in your PATH"; exit 1) +while [ -L $APPCFG ] +do + APPCFG=$(readlink $APPCFG) +done + +BIN_DIR=$(dirname $APPCFG) + +if [ "$(basename $BIN_DIR)" == "bin" ] +then + SDK_HOME=$(dirname $BIN_DIR) + if [ -d $SDK_HOME/platform/google_appengine ] + then + SDK_HOME=$SDK_HOME/platform/google_appengine + fi +else + SDK_HOME=$BIN_DIR +fi + +function get_app_id() { + local app_id + app_id=$( cat $ROOT_DIR/app.yaml | egrep '^application:' | sed 's/application: *\([0-9a-z][-0-9a-z]*[0-9a-z]\).*/\1/' ) + while [ $# -gt 0 ] + do + if [ "$1" == "-A" ] + then + shift + app_id=$1 + elif [ "${1/=*/}" == "--application" ] + then + app_id=${1/--application=/} + fi + shift + done + echo $app_id +} + +function deploy() { + echo -e "\n*** Rolling back any pending updates (just in case) ***\n" + appcfg.py --oauth2 $* rollback . + + echo -e "\n*** DEPLOYING ***\n" + appcfg.py --oauth2 $* update -V $VERSION . + + echo -e "\n*** SETTING DEFAULT VERSION ***\n" + appcfg.py --oauth2 $* set_default_version -V $VERSION . +} + +APP_ID=$(get_app_id $*) +echo +echo "Using app id: $APP_ID" + +deploy $* -A $APP_ID diff --git a/appengine/angular/scripts/run.sh b/appengine/angular/scripts/run.sh new file mode 100755 index 000000000000..a0345f56ad4f --- /dev/null +++ b/appengine/angular/scripts/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# +set -uex + +dev_appserver.py \ + --host 0.0.0.0 \ + --admin_host 127.0.0.1 \ + --skip_sdk_update_check yes \ + . $*