easierbycode
11/21/2013 - 9:53 PM

app.js

angular.module('myApp.services', [])
.factory('UserService', function($q, $http, AWSService, StripeService) {
  var service = {
    _user: null,
    UsersTable: "Users",
    UserItemsTable: "UsersItems",
    ChargeTable: "UserCharges",
    Bucket: 'ng-newsletter-example',
    setCurrentUser: function(u) {
      if (u && !u.error) {
        AWSService.setToken(u.id_token);
        return service.currentUser();
      } else {
        var d = $q.defer();
        d.reject(u.error);
        return d.promise;
      }
    },
    currentUser: function() {
      var d = $q.defer();
      if (service._user) {
        d.resolve(service._user);
      } else {
        AWSService.credentials().then(function() {
          gapi.client.oauth2.userinfo.get()
          .execute(function(e) {
            var email = e.email;
            AWSService.dynamo({
              params: {TableName: service.UsersTable}
            })
            .then(function(table) {
              table.getItem({
                Key: {
                  'User email': {
                    S: email
                  }
                }
              },
                function(err, data) {
                  if (Object.keys(data).length == 0) {
                    // User didn't previously exist
                    var itemParams = {
                      Item: {
                        'User email': {S: email}, 
                        data: {
                          S: JSON.stringify(e)
                        }
                      }
                    };
                    table.putItem(itemParams, function(err, data) {
                      service._user = e;
                      d.resolve(e);
                    })
                  } else {
                    service._user = JSON.parse(
                      data.Item.data.S
                    );
                    d.resolve(service._user);
                  }
              });
            })
          });
        });
      }
      return d.promise;
    },
    itemsForSale: function() {
      var d = $q.defer();
      service.currentUser().then(function(user) {
        AWSService.dynamo({
          params: {TableName: service.UserItemsTable}
        }).then(function(table) {
          table.query({
            TableName: service.UserItemsTable,
            KeyConditions: {
              "User email": {
                "ComparisonOperator": "EQ",
                "AttributeValueList": [
                  {S: user.email}
                ]
              }
            }
          }, function(err, data) {
            var items = [];
            if (data) {
              angular.forEach(data.Items, function(item) {
                items.push(JSON.parse(item.data.S));
              });
              d.resolve(items);
            } else {
              d.reject(err);
            }
          })
        });
      });
      return d.promise;
    },
    uploadItemForSale: function(items) {
      var d = $q.defer();
      service.currentUser().then(function(user) {
        AWSService.s3({
          params: {
            Bucket: service.Bucket
          }
        }).then(function(s3) {
          var file = items[0]; // only one at a time
          var params = {
            Key: file.name,
            Body: file,
            ContentType: file.type
          }

          s3.putObject(params, function(err, data) {
            // Also, let's get a url
            var params = {
              Bucket: service.Bucket, 
              Key: file.name, 
              Expires: 900*4 // 1 hour
            };
            s3.getSignedUrl('getObject', params, function (err, url) {
              AWSService.dynamo({
                params: {TableName: service.UserItemsTable}
              }).then(function(table) {
                var itemParams = {
                  Item: {
                    'ItemId': {S: file.name},
                    'User email': {S: user.email}, 
                    data: {
                      S: JSON.stringify({
                        itemId: file.name,
                        itemSize: file.size,
                        itemUrl: url
                      })
                    }
                  }
                };
                table.putItem(itemParams, function(err, data) {
                  d.resolve(data);
                });
              });
            });
          });
        });
      });
      return d.promise;
    },
    createPayment: function(item, charge) {
      var d = $q.defer();
      StripeService.createCharge(charge)
      .then(function(data) {
        var stripeToken = data.id;

        AWSService.sqs(
          {QueueName: service.ChargeTable}
        ).then(function(queue) {
          queue.sendMessage({
            MessageBody: JSON.stringify({
              item: item,
              stripeToken: stripeToken
            })
          }, function(err, data) {
            d.resolve(data);
          })
        })
      }, function(err) {
        d.reject(err);
      });
      return d.promise;
    }
  };

  return service;
})
.provider('AWSService', function() {
  var self = this;

  // Set defaults
  AWS.config.region = 'us-east-1';

  self.arn = null;

  self.setArn = function(arn) {
    if (arn) self.arn = arn;
  }

  self.setRegion = function(region) {
    if (region) AWS.config.region = region;
  }

  self.setLogger = function(logger) {
    if (logger) AWS.config.logger = logger;
  }
  
  self.$get = function($q, $cacheFactory) {
    var s3Cache = $cacheFactory('s3Cache'),
        dynamoCache = $cacheFactory('dynamo'),
        snsCache = $cacheFactory('sns'),
        sqsCache = $cacheFactory('sqs');
        credentialsDefer = $q.defer(),
        credentialsPromise = credentialsDefer.promise;

    return {
      credentials: function() {
        return credentialsPromise;
      },
      setToken: function(token, providerId) {
        var config = {
          RoleArn: self.arn,
          WebIdentityToken: token,
          RoleSessionName: 'web-id'
        }
        if (providerId) {
          config['ProviderId'] = providerId;
        }
        self.config = config;
        AWS.config.credentials = 
          new AWS.WebIdentityCredentials(config);
        credentialsDefer.resolve(AWS.config.credentials);
      },
      s3: function(params) {
        var d = $q.defer();
        credentialsPromise.then(function() {
          var s3Obj = s3Cache.get(JSON.stringify(params));
          if (!s3Obj) {
            var s3Obj = new AWS.S3(params);
            s3Cache.put(JSON.stringify(params), s3Obj);
          }
          d.resolve(s3Obj);
        });
        return d.promise;
      },
      dynamo: function(params) {
        var d = $q.defer();
        credentialsPromise.then(function() {
          var table = dynamoCache.get(JSON.stringify(params));
          if (!table) {
            var table = new AWS.DynamoDB(params);
            dynamoCache.put(JSON.stringify(params), table);
          };
          d.resolve(table);
        });
        return d.promise;
      },
      sns: function(params) {
        var d = $q.defer();
        credentialsPromise.then(function() {
          var sns = snsCache.get(JSON.stringify(params));
          if (!sns) {
            sns = new AWS.SNS(params);
            snsCache.put(JSON.stringify(params), sns);
          }
          d.resolve(sns);
        })
        return d.promise;
      },
      sqs: function(params) {
        var d = $q.defer();
        credentialsPromise.then(function() {
          var url = sqsCache.get(JSON.stringify(params)),
              queued = $q.defer();
          if (!url) {
            var sqs = new AWS.SQS();
            sqs.createQueue(params, function(err, data) {
              if (data) {
                url = data.QueueUrl;
                sqsCache.put(JSON.stringify(params), url);
                queued.resolve(url);
              } else {
                queued.reject(err);
              }
            });
          } else {
            queued.resolve(url);
          }
          queued.promise.then(function(url) {
            var queue = new AWS.SQS({params: {QueueUrl: url}});
            d.resolve(queue);
          });
        })
        return d.promise; 
      }
    }
  }
})
.provider('StripeService', function() {
  var self = this;

  self.setPublishableKey = function(key) {
    Stripe.setPublishableKey(key);
  }

  self.$get = function($q) {
    return {
      createCharge: function(obj) {
        var d = $q.defer();

        if (!obj.hasOwnProperty('number') ||
            !obj.hasOwnProperty('cvc') ||
            !obj.hasOwnProperty('exp_month') ||
            !obj.hasOwnProperty('exp_year')
          ) {
          d.reject("Bad input", obj);
        } else {
          Stripe.card.createToken(obj, 
            function(status, resp) {
              if (status == 200) {
                d.resolve(resp);
              } else {
                d.reject(status);
              }
          });
        }

        return d.promise;
      }
    }
  }
})
{
  "Version":"2012-10-17",
  "Statement":[
    {
      "Action":[
        "s3:GetObject",
        "s3:ListBucket",
        "s3:PutObject"
      ],
      "Sid":"Stmt1383614419000",
      "Resource":[
        "arn:aws:s3:::ng-newsletter-example/*"
      ],
      "Effect":"Allow"
    },
    {
      "Action":[
        "dynamodb:GetItem",
        "dynamodb:ListTables",
        "dynamodb:PutItem",
        "dynamodb:Query"
      ],
      "Sid":"Stmt1383614445000",
      "Resource":[
        "arn:aws:dynamodb:us-east-1:<ACCOUNT_ID>:table/Users",
        "arn:aws:dynamodb:us-east-1:<ACCOUNT_ID>:table/UsersItems"
      ],
      "Effect":"Allow"
    },
    {
      "Action":[
        "sns:ListSubscriptions",
        "sns:ListTopics",
        "sns:Publish",
        "sns:Subscribe"
      ],
      "Sid":"Stmt1383614467000",
      "Resource":[
        "arn:aws:sns:us-east-1:<ACCOUNT_ID>:upload_notifications"
      ],
      "Effect":"Allow"
    },
    {
      "Action":[
        "sqs:ReceiveMessage",
        "sqs:SendMessage"
      ],
      "Sid":"Stmt1383648082000",
      "Resource":[
        "arn:aws:sqs:us-east-1:<ACCOUNT_ID>:UserCharges"
      ],
      "Effect":"Allow"
    }
  ]
}
<div class="container">
  <h1>Home</h1>

  <div ng-show="!user" class="row">
    <div class="col-md-12">
      <h2>Signup or login to ngroad</h2>
      <div google-signin 
        client-id='395118764200' 
        after-signin="signedIn(oauth)"></div>
    </div>
  </div>

  <div ng-show="user">
    <pre>{{ user | json }}</pre>
    <div class="row"
      <div class="col-md-12">
        <div file-upload="onFile(files)"></div>
      </div>
      
      <div ng-show="images">
        <div class="col-sm-6 col-md-4" 
          ng-repeat="image in images">
          <div class="thumbnail">
            <img ng-click="sellImage(image)"
                data-ng-src="{{image.itemUrl}}" />
          </div>
        </div>
      </div>

      <div ng-show="showCC">
        <form ng-submit="submitPayment()">
          <span ng-bind="errors"></span>

          <label>
            <span>Card Number</span>
            <input type="text" 
                    ng-minlength="16"
                    ng-maxlength="20"
                    size="20"
                    data-stripe="number"
                    ng-model="charge.number" />
          </label>

          <label>
            <span>CVC</span>
            <input type="text" 
                    ng-minlength="3" 
                    ng-maxlength="4" 
                    data-stripe="cvc"
                    ng-model="charge.cvc" />
          </label>

          <label>
            <span>Expiration (MM/YYYY)</span>
            <input type="text" 
              ng-minlength="2" 
              ng-maxlength="2" 
              size="2" 
              data-stripe="exp_month"
              ng-model="charge.exp_month" />
          </label>
          <span> / </span>
          <input type="text" 
            ng-minlength="4" 
            ng-maxlength="4" 
            size="4" 
            data-stripe="exp-year"
            ng-model="charge.exp_year" />

          <input type="hidden"
            name="email"
            value="user.email" />

          <button type="submit">Submit Payment</button>
        </form>
      </div>

      <div ng-show="!images">
        <h4>You have nothing for sale. Upload something</h4>
      </div>

    </div>
  </div>  
</div>
<!doctype html>
<html>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular.min.js"></script>
    <script src="http://code.angularjs.org/1.2.0-rc.3/angular-route.min.js"></script>
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.0.0-rc1.min.js"></script>
    <link rel="stylesheet" href="styles/bootstrap.min.css">
  </head>
  <body>
    <div ng-view></div>
    <script src="scripts/app.js"></script>
    <script src="scripts/controllers.js"></script>
    <script src="scripts/services.js"></script>
    <script src="scripts/directives.js"></script>

    <script type="text/javascript" src="https://js.stripe.com/v2/"></script>
    <script type="text/javascript">
      (function() {
       var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
       po.src = 'https://apis.google.com/js/client:plusone.js?onload=onLoadCallback';
       var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
     })();
    </script>
  </body>
</html>
angular.module('myApp.directives', [])
.directive('googleSignin', function() {
  return {
    restrict: 'A',
    template: '<span id="signinButton"></span>',
    replace: true,
    scope: {
      afterSignin: '&'
    },
    link: function(scope, ele, attrs) {
      attrs.$set('class', 'g-signin');

      attrs.$set('data-clientid', 
          attrs.clientId+'.apps.googleusercontent.com');

      var scopes = attrs.scopes || [
        'auth/plus.login', 
        'auth/userinfo.email'
      ];

      var scopeUrls = [];

      for (var i = 0; i < scopes.length; i++) {
        scopeUrls.push('https://www.googleapis.com/' + scopes[i]);
      };

      var callbackId = "_googleSigninCallback",
          directiveScope = scope;
      window[callbackId] = function() {
        var oauth = arguments[0];
        directiveScope.afterSignin({oauth: oauth});
        window[callbackId] = null;
      };

      attrs.$set('data-callback', callbackId);
      attrs.$set('data-cookiepolicy', 'single_host_origin');
      attrs.$set('data-requestvisibleactions', 'http://schemas.google.com/AddActivity')
      attrs.$set('data-scope', scopeUrls.join(' '));

      (function() {
       var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
       po.src = 'https://apis.google.com/js/client:plusone.js';
       var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
      })();
    }
  }
})
.directive('fileUpload', function() {
  return {
    restrict: 'A',
    scope: {
      fileUpload: '&'
    },
    template: '<input type="file" id="file" /> ',
    replace: true,
    link: function(scope, ele, attrs) {
      ele.bind('change', function() {
        var file = ele[0].files;
        if (file) {
          scope.fileUpload({files: file});
        }
      })
    }
  }
})
angular.module('myApp')
.controller('MainCtrl', 
  function($scope, AWSService, UserService, StripeService) {
    $scope.signedIn = function(oauth) {
      UserService.setCurrentUser(oauth)
      .then(function(user) {
        $scope.user = user;
      });
    }

    var getItemsForSale = function() {
      UserService.itemsForSale()
      .then(function(images) {
        $scope.images = images;
      });
    }

    $scope.onFile = function(files) {
      UserService.uploadItemForSale(files)
      .then(function(data) {
        getItemsForSale();
      });
    }

    $scope.sellImage = function(image) {
      $scope.showCC = true;
      $scope.currentItem = image;
    }

    $scope.submitPayment = function() {
      UserService
        .createPayment($scope.currentItem, $scope.charge)
      .then(function(data) {
        $scope.showCC = false;
      });
    }

    getItemsForSale();
});
angular.module('myApp', 
  ['ngRoute', 'myApp.services', 'myApp.directives']
)
.config(function(AWSServiceProvider) {
  AWSServiceProvider.setArn('arn:aws:iam::<ACCOUNT_ID>:role/google-web-role');
})
.config(function(StripeServiceProvider) {
  StripeServiceProvider.setPublishableKey('pk_test_YOURKEY');
})
.config(function($routeProvider) {
  $routeProvider
  .when('/', {
    controller: 'MainCtrl',
    templateUrl: 'templates/main.html',
  })
  .otherwise({
    redirectTo: '/'
  });
});

window.onLoadCallback = function() {
  angular.element(document).ready(function() {
    gapi.client.load('oauth2', 'v2', function() {
      angular.bootstrap(
        document,
        ['myApp']
      );
    });
  });
}