Browse Source

Step 2: Users can post new websites if they are logged in.

- Websites posted by users should have an URL and a description.
- URLs must match a simple regex.
- URL, title and description may not be empty
- Access is denied otherwise
Frederic G. MARAND 9 years ago
parent
commit
8d6528e2d7

+ 5 - 1
.eslintrc.js

@@ -16,10 +16,14 @@ module.exports = {
     "Accounts": true,
     "Session": true,
 
+    // From check.
+    "check": true,
+
     // From iron:router.
     "Router": true,
 
     // from our app.
+    "isLoggedIn": true,
     "Websites": true
   },
 
@@ -199,7 +203,7 @@ module.exports = {
     "no-spaced-func": 1, // disallow space between function identifier and application
     "no-ternary": 0, // disallow the use of ternary operators (off by default)
     "no-trailing-spaces": 1, // disallow trailing whitespace at the end of lines
-    "no-underscore-dangle": 1, // disallow dangling underscores in identifiers
+    "no-underscore-dangle": 0, // disallow dangling underscores in identifiers
     "object-curly-spacing": [2, "always"],
     "one-var": [2, "never"], // allow just one var statement per function (off by default)
     "operator-assignment": [1, "never"], // require assignment operator shorthand where possible or prohibit it entirely (off by default)

+ 1 - 1
.meteor/packages

@@ -17,7 +17,7 @@ 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)
 twbs:bootstrap
 accounts-ui
 accounts-password
+check

+ 0 - 1
.meteor/versions

@@ -35,7 +35,6 @@ html-tools@1.0.5
 htmljs@1.0.5
 http@1.1.1
 id-map@1.0.4
-insecure@1.0.4
 jquery@1.11.4
 launch-screen@1.0.4
 less@2.5.1

+ 0 - 59
client/client.js

@@ -4,62 +4,3 @@
 Accounts.ui.config({
   passwordSignupFields: "USERNAME_ONLY"
 });
-
-/**
- * Template helpers.
- */
-
-// helper function that returns all available websites
-Template.website_list.helpers({
-  websites: () => {
-    return Websites.find({});
-  }
-});
-
-/**
- * Template events
- */
-
-Template.website_item.events({
-  "click .js-upvote": function (event) {
-    // Example of how you can access the id for the website in the database
-    // (this is the data context for the template)
-    const websiteId = this._id;
-    console.log("Up voting website with id " + websiteId);
-    // Put the code in here to add a vote to a website!
-
-    // Prevent the button from reloading the page.
-    return false;
-  },
-
-  "click .js-downvote": function (event) {
-
-    // example of how you can access the id for the website in the database
-    // (this is the data context for the template)
-    const websiteId = this._id;
-    console.log("Down voting website with id " + websiteId);
-
-    // Put the code in here to remove a vote from a website!
-
-    // Prevent the button from reloading the page
-    return false;
-  }
-});
-
-Template.website_form.events({
-  "click .js-toggle-website-form": function () {
-    $("#website_form").toggle("slow");
-  },
-
-  "submit .js-save-website-form": function (event) {
-
-    // here is an example of how to get the url out of the form:
-    const url = event.target.url.value;
-    console.log("The url they entered is: " + url);
-
-    //  put your website saving code in here!
-
-    // Stop the form submit from reloading the page
-    return false;
-  }
-});

+ 3 - 0
client/website_form.html

@@ -1,7 +1,10 @@
 <template name="website_form">
+  {{#if isLoggedIn }}
   <a class="btn btn-default js-toggle-website-form" href="#">
     <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
   </a>
+  {{/if}}
+
   <div id="website_form" class="hidden_div">
     <form class="js-save-website-form">
       <div class="form-group">

+ 29 - 0
client/website_form.js

@@ -0,0 +1,29 @@
+/**
+ * Client code for the website_form template.
+ *
+ * - events
+ */
+
+Template.website_form.events({
+  "click .js-toggle-website-form": function () {
+    $("#website_form").toggle("slow");
+  },
+
+  "submit .js-save-website-form": function (event) {
+
+    // here is an example of how to get the url out of the form:
+    const url = event.target.url.value;
+    const title = event.target.title.value;
+    const description = event.target.description.value;
+
+    //  put your website saving code in here!
+    const doc = { url, title, description };
+    Meteor._debug("Client trying to insert", doc);
+    Websites.insert(doc);
+
+    // Stop the form submit from reloading the page
+    return false;
+  }
+});
+
+Template.website_form.helpers({ isLoggedIn });

+ 33 - 0
client/website_item.js

@@ -0,0 +1,33 @@
+/**
+ * Client code for the website_item template.
+ */
+
+/**
+ * Template events
+ */
+
+Template.website_item.events({
+  "click .js-upvote": function (event) {
+    // Example of how you can access the id for the website in the database
+    // (this is the data context for the template)
+    const websiteId = this._id;
+    console.log("Up voting website with id " + websiteId);
+    // Put the code in here to add a vote to a website!
+
+    // Prevent the button from reloading the page.
+    return false;
+  },
+
+  "click .js-downvote": function (event) {
+
+    // example of how you can access the id for the website in the database
+    // (this is the data context for the template)
+    const websiteId = this._id;
+    console.log("Down voting website with id " + websiteId);
+
+    // Put the code in here to remove a vote from a website!
+
+    // Prevent the button from reloading the page
+    return false;
+  }
+});

+ 1 - 2
client/website_list.html

@@ -2,8 +2,7 @@
 <template name="website_list">
 	<ol>
 	{{#each websites}}
-	{{>website_item}}
+  	{{>website_item}}
 	{{/each}}
 	</ol>
 </template>
-

+ 10 - 0
client/website_list.js

@@ -0,0 +1,10 @@
+/**
+ * Client code for the website_list template.
+ */
+
+// Helper function that returns all available websites
+Template.website_list.helpers({
+  websites: () => {
+    return Websites.find({});
+  }
+});

+ 5 - 0
lib/collections.js

@@ -1 +1,6 @@
 Websites = new Mongo.Collection("websites");
+
+Websites.validate = function () {
+
+};
+// See security in server/collections.js

+ 9 - 0
lib/lib.js

@@ -0,0 +1,9 @@
+/**
+ * Is current user logged in ?
+ *
+ * @returns {boolean}
+ *   True if the user is logged in, false otherwise.
+ */
+isLoggedIn = () => {
+  return !!Meteor.userId();
+};

+ 47 - 0
server/collections.js

@@ -0,0 +1,47 @@
+Websites.allow({
+  insert: function (userId, doc) {
+    // Ensure sane arguments.
+    check(doc, {
+      url: String,
+      title: String,
+      description: String
+    });
+
+    // Reject anonymous inserts.
+    if (!userId) {
+      throw new Meteor.Error("logged-out", "User must be logged in to post a site.");
+      // return false;
+    }
+
+    // Reject non-new inserts.
+    const url = doc.url;
+
+    if (Websites.findOne({ url })) {
+      throw new Meteor.Error("duplicate", "User may only post new sites.");
+      // return false;
+    }
+
+    // Reject wrong-looking URLs
+    // TODO: find a validation package usable server-side.
+    // The popular themeteorchef:jquery-validation appears to be client-only.
+    // For now using a very limited check.
+    const URL_BOGO_REGEX = /^https?:\/\/.+$/;
+    if (!URL_BOGO_REGEX.test(doc.url)) {
+      throw new Meteor.Error("bad-url", "Users may only post http(s) URLs.");
+      // return false;
+    }
+
+    // Reject empty titles and descriptions.
+    if (doc.title === "") {
+      throw new Meteor.Error("empty-title", "Title may not be empty");
+      // return false;
+    }
+
+    if (doc.description === "") {
+      throw new Meteor.Error("empty-description", "Description may not be empty");
+      // return false;
+    }
+
+    return true;
+  }
+});