diff --git a/History.md b/History.md
index aa4f68f555..9cdb1f08a2 100644
--- a/History.md
+++ b/History.md
@@ -230,13 +230,15 @@
### `meteor` command-line tool
-* There are two new example apps that can be used as starting points for
- developing Meteor apps with React and Angular. They include the necessary
- packages for the relevant view layer and don't include Blaze. Use them with:
+* You can now create three new example apps with the command line tool. These
+ are the apps from the official tutorials at http://meteor.com/tutorials, which
+ demonstrate building the same app with Blaze, Angular, and React. Try these
+ apps with:
```sh
- meteor create --example react
- meteor create --example angular
+ meteor create --example simple-todos
+ meteor create --example simple-todos-react
+ meteor create --example simple-todos-angular
```
* `meteor shell` no longer crashes when piped from another command.
diff --git a/examples/react/.meteor/packages b/examples/react/.meteor/packages
deleted file mode 100644
index a7096bcd4f..0000000000
--- a/examples/react/.meteor/packages
+++ /dev/null
@@ -1,22 +0,0 @@
-# Meteor packages used by this project, one per line.
-# Check this file (and the other files in this directory) into your repository.
-#
-# 'meteor add' and 'meteor remove' will edit this file for you,
-# but you can also edit it by hand.
-
-meteor-base # Packages every Meteor app needs to have
-mobile-experience # Packages for a great mobile UX
-mongo # The database Meteor supports right now
-session # Client-side reactive dictionary for your app
-jquery # Helpful client-side library
-tracker # Meteor's client-side reactive programming library
-
-standard-minifiers # JS/CSS minifiers run for production mode
-es5-shim # ECMAScript 5 compatibility for older browsers.
-ecmascript # Enable ECMAScript2015+ syntax in app code
-
-autopublish # Publish all data to the clients (for prototyping)
-insecure # Allow all DB writes from clients (for prototyping)
-
-static-html # Render static HTML content
-react # Everything you need to use React with Meteor
diff --git a/examples/react/.meteor/release b/examples/react/.meteor/release
deleted file mode 100644
index 69acc9f344..0000000000
--- a/examples/react/.meteor/release
+++ /dev/null
@@ -1 +0,0 @@
-METEOR@1.2-rc.15
diff --git a/examples/react/.meteor/versions b/examples/react/.meteor/versions
deleted file mode 100644
index 1f51e9a214..0000000000
--- a/examples/react/.meteor/versions
+++ /dev/null
@@ -1,73 +0,0 @@
-autopublish@1.0.4-rc.0
-autoupdate@1.2.3-rc.1
-babel-compiler@5.8.22-rc.1
-babel-runtime@0.1.4-rc.0
-base64@1.0.4-rc.0
-binary-heap@1.0.4-rc.0
-blaze@2.1.3-rc.0
-blaze-tools@1.0.4-rc.0
-boilerplate-generator@1.0.4-rc.1
-caching-compiler@1.0.0-rc.0
-caching-html-compiler@1.0.1-rc.0
-callback-hook@1.0.4-rc.0
-check@1.0.6-rc.0
-coffeescript@1.0.8-rc.4
-cosmos:browserify@0.4.0
-ddp@1.2.1-rc.0
-ddp-client@1.2.1-rc.2
-ddp-common@1.2.1-rc.0
-ddp-server@1.2.1-rc.2
-deps@1.0.8-rc.0
-diff-sequence@1.0.1-rc.0
-ecmascript@0.1.3-rc.3
-ecmascript-collections@0.1.5-rc.1
-ejson@1.0.7-rc.0
-es5-shim@0.1.0-rc.0
-fastclick@1.0.7-rc.0
-geojson-utils@1.0.4-rc.0
-hot-code-push@1.0.0-rc.0
-html-tools@1.0.5-rc.0
-htmljs@1.0.5-rc.1
-http@1.1.1-rc.1
-id-map@1.0.4-rc.0
-insecure@1.0.4-rc.0
-jquery@1.11.4-rc.0
-jsx@0.2.1
-launch-screen@1.0.3-rc.1
-livedata@1.0.14-rc.0
-logging@1.0.8-rc.1
-meteor@1.1.7-rc.2
-meteor-base@1.0.1-rc.0
-minifiers@1.1.6-rc.1
-minimongo@1.0.9-rc.0
-mobile-experience@1.0.1-rc.0
-mobile-status-bar@1.0.5-rc.1
-mongo@1.1.1-rc.4
-mongo-id@1.0.1-rc.0
-npm-mongo@1.4.39-rc.0_1
-observe-sequence@1.0.7-rc.0
-ordered-dict@1.0.4-rc.0
-promise@0.4.8-rc.0
-random@1.0.4-rc.0
-react@0.1.12
-react-meteor-data@0.1.8
-react-runtime@0.13.3_6
-react-runtime-dev@0.13.3_5
-react-runtime-prod@0.13.3_4
-reactive-dict@1.1.1-rc.0
-reactive-var@1.0.6-rc.0
-reload@1.1.4-rc.0
-retry@1.0.4-rc.0
-routepolicy@1.0.6-rc.0
-session@1.1.1-rc.0
-spacebars@1.0.7-rc.0
-spacebars-compiler@1.0.7-rc.0
-standard-minifiers@1.0.0-rc.2
-static-html@1.0.0-rc.5
-templating-tools@1.0.0-rc.0
-tracker@1.0.8-rc.0
-ui@1.0.7-rc.0
-underscore@1.0.4-rc.0
-url@1.0.5-rc.0
-webapp@1.2.2-rc.3
-webapp-hashing@1.0.4-rc.0
diff --git a/examples/react/index.html b/examples/react/index.html
deleted file mode 100644
index ee14011d45..0000000000
--- a/examples/react/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
diff --git a/examples/react/index.jsx b/examples/react/index.jsx
deleted file mode 100644
index 72b1dfc6f9..0000000000
--- a/examples/react/index.jsx
+++ /dev/null
@@ -1,65 +0,0 @@
-// A collection that is synchronized across client and server with the
-// 'autopublish' package. To control where the data is accessible from, remove
-// 'autopublish' and use Meteor.publish and Meteor.subscribe
-Items = new Mongo.Collection("items");
-
-// A React component, defined in the normal way
-App = React.createClass({
- mixins: [ReactMeteorData],
-
- // Load data from collections inside this special method enabled by the
- // ReactMeteorData mixin. The results are attached to this.data on the
- // component
- getMeteorData() {
- return {
- items: Items.find().fetch()
- };
- },
-
- addItem() {
- const nextIndex = Items.find().count() + 1;
-
- // We can insert from the client because we have the 'insecure' package
- // installed. Remove it and use Meteor methods for better security
- Items.insert({
- text: "Hello world! " + nextIndex
- });
- },
-
- renderItems() {
- return this.data.items.map((item) => {
- return (
-
- {item.text}
-
- );
- });
- },
-
- render() {
- return (
-
-
- {this.renderItems()}
-
-
-
-
- )
- }
-});
-
-if (Meteor.isClient) {
- // Code here runs on the client only
-
- Meteor.startup(() => {
- // Make sure to render after startup so the DOM is ready
- React.render(, document.getElementById("react-container"));
- });
-}
-
-if (Meteor.isServer) {
- // Code inside here will run on the server only
-}
diff --git a/examples/react/style.css b/examples/react/style.css
deleted file mode 100644
index b6b4052b43..0000000000
--- a/examples/react/style.css
+++ /dev/null
@@ -1 +0,0 @@
-/* CSS declarations go here */
diff --git a/examples/react/.meteor/.finished-upgraders b/examples/simple-todos-angular/.meteor/.finished-upgraders
similarity index 75%
rename from examples/react/.meteor/.finished-upgraders
rename to examples/simple-todos-angular/.meteor/.finished-upgraders
index cbcc9f90c6..8a761038c5 100644
--- a/examples/react/.meteor/.finished-upgraders
+++ b/examples/simple-todos-angular/.meteor/.finished-upgraders
@@ -6,6 +6,3 @@ notices-for-0.9.0
notices-for-0.9.1
0.9.4-platform-file
notices-for-facebook-graph-api-2
-1.2.0-standard-minifiers-package
-1.2.0-meteor-platform-split
-1.2.0-cordova-changes
diff --git a/examples/react/.meteor/.gitignore b/examples/simple-todos-angular/.meteor/.gitignore
similarity index 100%
rename from examples/react/.meteor/.gitignore
rename to examples/simple-todos-angular/.meteor/.gitignore
diff --git a/examples/simple-todos-angular/.meteor/packages b/examples/simple-todos-angular/.meteor/packages
new file mode 100644
index 0000000000..2d0433a829
--- /dev/null
+++ b/examples/simple-todos-angular/.meteor/packages
@@ -0,0 +1,10 @@
+# Meteor packages used by this project, one per line.
+# Check this file (and the other files in this directory) into your repository.
+#
+# 'meteor add' and 'meteor remove' will edit this file for you,
+# but you can also edit it by hand.
+
+meteor-platform
+urigo:angular
+accounts-ui
+accounts-password
diff --git a/examples/react/.meteor/platforms b/examples/simple-todos-angular/.meteor/platforms
similarity index 100%
rename from examples/react/.meteor/platforms
rename to examples/simple-todos-angular/.meteor/platforms
diff --git a/examples/simple-todos-angular/.meteor/release b/examples/simple-todos-angular/.meteor/release
new file mode 100644
index 0000000000..dab6b552c0
--- /dev/null
+++ b/examples/simple-todos-angular/.meteor/release
@@ -0,0 +1 @@
+METEOR@1.1.0.2
diff --git a/examples/simple-todos-angular/.meteor/versions b/examples/simple-todos-angular/.meteor/versions
new file mode 100644
index 0000000000..96bf8b78b8
--- /dev/null
+++ b/examples/simple-todos-angular/.meteor/versions
@@ -0,0 +1,61 @@
+accounts-base@1.2.0
+accounts-password@1.1.1
+accounts-ui@1.1.5
+accounts-ui-unstyled@1.1.7
+angular:angular@1.4.2
+autoupdate@1.2.1
+base64@1.0.3
+binary-heap@1.0.3
+blaze@2.1.2
+blaze-tools@1.0.3
+boilerplate-generator@1.0.3
+callback-hook@1.0.3
+check@1.0.5
+dburles:mongo-collection-instances@0.3.4
+ddp@1.1.0
+deps@1.0.7
+ejson@1.0.6
+email@1.0.6
+fastclick@1.0.3
+geojson-utils@1.0.3
+html-tools@1.0.4
+htmljs@1.0.4
+http@1.1.0
+id-map@1.0.3
+jquery@1.11.3_2
+json@1.0.3
+lai:collection-extensions@0.1.4
+launch-screen@1.0.2
+less@1.0.14
+livedata@1.0.13
+localstorage@1.0.3
+logging@1.0.7
+meteor@1.1.6
+meteor-platform@1.2.2
+minifiers@1.1.5
+minimongo@1.0.8
+mobile-status-bar@1.0.3
+mongo@1.1.0
+npm-bcrypt@0.7.8_2
+observe-sequence@1.0.6
+ordered-dict@1.0.3
+random@1.0.3
+reactive-dict@1.1.0
+reactive-var@1.0.5
+reload@1.1.3
+retry@1.0.3
+routepolicy@1.0.5
+service-configuration@1.0.4
+session@1.1.0
+sha@1.0.3
+spacebars@1.0.6
+spacebars-compiler@1.0.6
+srp@1.0.3
+templating@1.1.1
+tracker@1.0.7
+ui@1.0.6
+underscore@1.0.3
+urigo:angular@0.9.2
+url@1.0.4
+webapp@1.2.0
+webapp-hashing@1.0.3
diff --git a/examples/simple-todos-angular/README.md b/examples/simple-todos-angular/README.md
new file mode 100644
index 0000000000..258e5f8975
--- /dev/null
+++ b/examples/simple-todos-angular/README.md
@@ -0,0 +1,9 @@
+# Simple Todo List
+
+The Meteor Tutorial app, angular-meteor version.
+
+Use it to share a single todo list with your friends. The list updates on everyone's screen in real time, and you can make tasks private if you don't want others to see them.
+
+Learn how to build this app by following the [Meteor Tutorial - Angular version](https://www.meteor.com/tutorials/angular/creating-an-app).
+
+
diff --git a/examples/simple-todos-angular/simple-todos-angular.css b/examples/simple-todos-angular/simple-todos-angular.css
new file mode 100644
index 0000000000..0bca5d2ada
--- /dev/null
+++ b/examples/simple-todos-angular/simple-todos-angular.css
@@ -0,0 +1,127 @@
+/* CSS declarations go here */
+body {
+ font-family: sans-serif;
+ background-color: #315481;
+ background-image: linear-gradient(to bottom, #315481, #918e82 100%);
+ background-attachment: fixed;
+
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+
+ padding: 0;
+ margin: 0;
+
+ font-size: 14px;
+}
+
+.container {
+ max-width: 600px;
+ margin: 0 auto;
+ min-height: 100%;
+ background: white;
+}
+
+header {
+ background: #d2edf4;
+ background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%);
+ padding: 20px 15px 15px 15px;
+ position: relative;
+}
+
+#login-buttons {
+ display: block;
+}
+
+h1 {
+ font-size: 1.5em;
+ margin: 0;
+ margin-bottom: 10px;
+ display: inline-block;
+ margin-right: 1em;
+}
+
+form {
+ margin-top: 10px;
+ margin-bottom: -10px;
+ position: relative;
+}
+
+.new-task input {
+ box-sizing: border-box;
+ padding: 10px 0;
+ background: transparent;
+ border: none;
+ width: 100%;
+ padding-right: 80px;
+ font-size: 1em;
+}
+
+.new-task input:focus{
+ outline: 0;
+}
+
+ul {
+ margin: 0;
+ padding: 0;
+ background: white;
+}
+
+.delete {
+ float: right;
+ font-weight: bold;
+ background: none;
+ font-size: 1em;
+ border: none;
+ position: relative;
+}
+
+li {
+ position: relative;
+ list-style: none;
+ padding: 15px;
+ border-bottom: #eee solid 1px;
+}
+
+li .text {
+ margin-left: 10px;
+}
+
+li.checked {
+ color: #888;
+}
+
+li.checked .text {
+ text-decoration: line-through;
+}
+
+li.private {
+ background: #eee;
+ border-color: #ddd;
+}
+
+header .hide-completed {
+ float: right;
+}
+
+.toggle-private {
+ margin-left: 5px;
+}
+
+@media (max-width: 600px) {
+ li {
+ padding: 12px 15px;
+ }
+
+ .search {
+ width: 150px;
+ clear: both;
+ }
+
+ .new-task input {
+ padding-bottom: 5px;
+ }
+}
+
diff --git a/examples/simple-todos-angular/simple-todos-angular.html b/examples/simple-todos-angular/simple-todos-angular.html
new file mode 100644
index 0000000000..e43eceb5c9
--- /dev/null
+++ b/examples/simple-todos-angular/simple-todos-angular.html
@@ -0,0 +1,7 @@
+
+ Todo List
+
+
+
+
diff --git a/examples/simple-todos-angular/simple-todos-angular.js b/examples/simple-todos-angular/simple-todos-angular.js
new file mode 100644
index 0000000000..1d371fb3ca
--- /dev/null
+++ b/examples/simple-todos-angular/simple-todos-angular.js
@@ -0,0 +1,113 @@
+Tasks = new Mongo.Collection('tasks');
+
+if (Meteor.isClient) {
+
+ Accounts.ui.config({
+ passwordSignupFields: "USERNAME_ONLY"
+ });
+
+ // This code only runs on the client
+ angular.module('simple-todos',['angular-meteor']);
+
+ function onReady() {
+ angular.bootstrap(document, ['simple-todos']);
+ }
+
+ if (Meteor.isCordova)
+ angular.element(document).on('deviceready', onReady);
+ else
+ angular.element(document).ready(onReady);
+
+ angular.module('simple-todos').controller('TodosListCtrl', ['$scope', '$meteor',
+ function ($scope, $meteor) {
+
+ $scope.$meteorSubscribe('tasks');
+
+ $scope.tasks = $meteor.collection(function() {
+ return Tasks.find($scope.getReactively('query'), {sort: {createdAt: -1}})
+ });
+
+ $scope.addTask = function (newTask) {
+ $meteor.call('addTask', newTask);
+ };
+
+ $scope.deleteTask = function (task) {
+ $meteor.call('deleteTask', task._id);
+ };
+
+ $scope.setChecked = function (task) {
+ $meteor.call('setChecked', task._id, !task.checked);
+ };
+
+ $scope.setPrivate = function (task) {
+ $meteor.call('setPrivate', task._id, ! task.private);
+ };
+
+ $scope.$watch('hideCompleted', function() {
+ if ($scope.hideCompleted)
+ $scope.query = {checked: {$ne: true}};
+ else
+ $scope.query = {};
+ });
+
+ $scope.incompleteCount = function () {
+ return Tasks.find({ checked: {$ne: true} }).count();
+ };
+
+ }]);
+}
+
+Meteor.methods({
+ addTask: function (text) {
+ // Make sure the user is logged in before inserting a task
+ if (! Meteor.userId()) {
+ throw new Meteor.Error('not-authorized');
+ }
+
+ Tasks.insert({
+ text: text,
+ createdAt: new Date(),
+ owner: Meteor.userId(),
+ username: Meteor.user().username
+ });
+ },
+ deleteTask: function (taskId) {
+ var task = Tasks.findOne(taskId);
+ if (task.private && task.owner !== Meteor.userId()) {
+ // If the task is private, make sure only the owner can delete it
+ throw new Meteor.Error('not-authorized');
+ }
+
+ Tasks.remove(taskId);
+ },
+ setChecked: function (taskId, setChecked) {
+ var task = Tasks.findOne(taskId);
+ if (task.private && task.owner !== Meteor.userId()) {
+ // If the task is private, make sure only the owner can check it off
+ throw new Meteor.Error('not-authorized');
+ }
+
+ Tasks.update(taskId, { $set: { checked: setChecked} });
+ },
+ setPrivate: function (taskId, setToPrivate) {
+ var task = Tasks.findOne(taskId);
+
+ // Make sure only the task owner can make a task private
+ if (task.owner !== Meteor.userId()) {
+ throw new Meteor.Error('not-authorized');
+ }
+
+ Tasks.update(taskId, { $set: { private: setToPrivate } });
+ }
+});
+
+if (Meteor.isServer) {
+ Meteor.publish('tasks', function () {
+ return Tasks.find({
+ $or: [
+ { private: {$ne: true} },
+ { owner: this.userId }
+ ]
+ });
+ });
+}
diff --git a/examples/simple-todos-angular/todos-list.ng.html b/examples/simple-todos-angular/todos-list.ng.html
new file mode 100644
index 0000000000..1799364849
--- /dev/null
+++ b/examples/simple-todos-angular/todos-list.ng.html
@@ -0,0 +1,39 @@
+
+ );
+ }
+});
diff --git a/examples/simple-todos-react/LICENSE b/examples/simple-todos-react/LICENSE
new file mode 100644
index 0000000000..e41973a6e1
--- /dev/null
+++ b/examples/simple-todos-react/LICENSE
@@ -0,0 +1,22 @@
+========================================
+Meteor is licensed under the MIT License
+========================================
+
+Copyright (C) 2011--2015 Meteor Development Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/examples/simple-todos-react/README.md b/examples/simple-todos-react/README.md
new file mode 100644
index 0000000000..214dd3a445
--- /dev/null
+++ b/examples/simple-todos-react/README.md
@@ -0,0 +1,9 @@
+# Simple Todo List
+
+The Meteor Tutorial app.
+
+Use it to share a single todo list with your friends. The list updates on everyone's screen in real time, and you can make tasks private if you don't want others to see them.
+
+Learn how to build this app by following the [Meteor Tutorial](http://www.meteor.com/install).
+
+
\ No newline at end of file
diff --git a/examples/simple-todos-react/Task.jsx b/examples/simple-todos-react/Task.jsx
new file mode 100644
index 0000000000..37eadd38b8
--- /dev/null
+++ b/examples/simple-todos-react/Task.jsx
@@ -0,0 +1,52 @@
+// Task component - represents a single todo item
+Task = React.createClass({
+ propTypes: {
+ task: React.PropTypes.object.isRequired,
+ showPrivateButton: React.PropTypes.bool.isRequired
+ },
+
+ toggleChecked() {
+ // Set the checked property to the opposite of its current value
+ Meteor.call("setChecked", this.props.task._id, ! this.props.task.checked);
+ },
+
+ deleteThisTask() {
+ Meteor.call("removeTask", this.props.task._id);
+ },
+
+ togglePrivate() {
+ Meteor.call("setPrivate", this.props.task._id, ! this.props.task.private);
+ },
+
+ render() {
+ // Give tasks a different className when they are checked off,
+ // so that we can style them nicely in CSS
+ // Add "checked" and/or "private" to the className when needed
+ const taskClassName = (this.props.task.checked ? "checked" : "") + " " +
+ (this.props.task.private ? "private" : "");
+
+ return (
+
+
diff --git a/examples/simple-todos/simple-todos.js b/examples/simple-todos/simple-todos.js
new file mode 100644
index 0000000000..9bf41bbfc8
--- /dev/null
+++ b/examples/simple-todos/simple-todos.js
@@ -0,0 +1,123 @@
+Tasks = new Mongo.Collection("tasks");
+
+if (Meteor.isServer) {
+ // This code only runs on the server
+ // Only publish tasks that are public or belong to the current user
+ Meteor.publish("tasks", function () {
+ return Tasks.find({
+ $or: [
+ { private: {$ne: true} },
+ { owner: this.userId }
+ ]
+ });
+ });
+}
+
+if (Meteor.isClient) {
+ // This code only runs on the client
+ Meteor.subscribe("tasks");
+
+ Template.body.helpers({
+ tasks: function () {
+ if (Session.get("hideCompleted")) {
+ // If hide completed is checked, filter tasks
+ return Tasks.find({checked: {$ne: true}}, {sort: {createdAt: -1}});
+ } else {
+ // Otherwise, return all of the tasks
+ return Tasks.find({}, {sort: {createdAt: -1}});
+ }
+ },
+ hideCompleted: function () {
+ return Session.get("hideCompleted");
+ },
+ incompleteCount: function () {
+ return Tasks.find({checked: {$ne: true}}).count();
+ }
+ });
+
+ Template.body.events({
+ "submit .new-task": function (event) {
+ // Prevent default browser form submit
+ event.preventDefault();
+
+ // Get value from form element
+ var text = event.target.text.value;
+
+ // Insert a task into the collection
+ Meteor.call("addTask", text);
+
+ // Clear form
+ event.target.text.value = "";
+ },
+ "change .hide-completed input": function (event) {
+ Session.set("hideCompleted", event.target.checked);
+ }
+ });
+
+ Template.task.helpers({
+ isOwner: function () {
+ return this.owner === Meteor.userId();
+ }
+ });
+
+ Template.task.events({
+ "click .toggle-checked": function () {
+ // Set the checked property to the opposite of its current value
+ Meteor.call("setChecked", this._id, ! this.checked);
+ },
+ "click .delete": function () {
+ Meteor.call("deleteTask", this._id);
+ },
+ "click .toggle-private": function () {
+ Meteor.call("setPrivate", this._id, ! this.private);
+ }
+ });
+
+ Accounts.ui.config({
+ passwordSignupFields: "USERNAME_ONLY"
+ });
+}
+
+Meteor.methods({
+ addTask: function (text) {
+ // Make sure the user is logged in before inserting a task
+ if (! Meteor.userId()) {
+ throw new Meteor.Error("not-authorized");
+ }
+
+ Tasks.insert({
+ text: text,
+ createdAt: new Date(),
+ owner: Meteor.userId(),
+ username: Meteor.user().username
+ });
+ },
+ deleteTask: function (taskId) {
+ var task = Tasks.findOne(taskId);
+ if (task.private && task.owner !== Meteor.userId()) {
+ // If the task is private, make sure only the owner can delete it
+ throw new Meteor.Error("not-authorized");
+ }
+
+ Tasks.remove(taskId);
+ },
+ setChecked: function (taskId, setChecked) {
+ var task = Tasks.findOne(taskId);
+ if (task.private && task.owner !== Meteor.userId()) {
+ // If the task is private, make sure only the owner can check it off
+ throw new Meteor.Error("not-authorized");
+ }
+
+ Tasks.update(taskId, { $set: { checked: setChecked} });
+ },
+ setPrivate: function (taskId, setToPrivate) {
+ var task = Tasks.findOne(taskId);
+
+ // Make sure only the task owner can make a task private
+ if (task.owner !== Meteor.userId()) {
+ throw new Meteor.Error("not-authorized");
+ }
+
+ Tasks.update(taskId, { $set: { private: setToPrivate } });
+ }
+});