#MeteoReddit

The fastest built reddit clone you've ever seen homie.

###What's in it?

  • Meteor
  • Iron Router
  • Blaze
  • AccountsUI
  • scss
  • materialize-scss
  • useraccounts Materialize
  • browser-policy
  • alanning:roles
  • cunneen:accounts-admin-materializecss

###Getting Started

First, we Install meteor. Second, we start er' up.

NOTE: If you have problems with ANYTHING on windows, try using CMD or PowerShell instead of gitbash to start your meteor instance.

meteor create myredditclone
cd myredditclone
touch README.md
npm install
meteor run #or meteor

Start tracking on github. Like, now. Visit localhost:3000 in your browser. To use robomongo, connect to port 3001 To use minimongo console, type mongo in the terminal while in the myredditclone dir.

Meteor will livereload EVERYTHING. Enjoy that.

###Adding Dependencies Up Front

Meteor has it's own package system called Atmosphere that installs dependencies to ./meteor/package using meteor add <dep name>. You can also use npm i -S <package name> to get node modules. You would import node modules the same way you are used to. Meteor packages are automatically available.

Let's get our dependencies installed. copy paste the following into the bottom of ./meteor/packages (bypassing the meteor add or npm install step.)

fourseven:scss
poetic:materialize-scss
useraccounts:materialize
useraccounts:core
useraccounts:iron-routing
accounts-password@1.3.1
browser-policy@1.0.9
browser-policy-common@1.0.11
browser-policy-content@1.0.12
browser-policy-framing@1.0.12
alanning:roles
cunneen:accounts-admin-materializecss

We get Scss, materialize-scss, useraccounts (for authentication), Roles (for authorization), browser-policy (for CORS), and an admin panel for account control.

Delete the following lines from the packages file as well:

autopublish@1.0.7             # Publish all data to the clients (for prototyping)
insecure@1.0.7                # Allow all DB writes from clients (for prototyping)

As a final step run meteor npm install --save bcrypt.

###Create An Index Route and a Layout

Lets create an Iron Router. In the root of your project, make a new folder called lib. In the lib folder, make a router.js and paste in the following:

// In the configuration, we declare the layout, 404, loading,
// navbar, and footer templates.
Router.configure({
  layoutTemplate: 'masterLayout',
  loadingTemplate: 'loading',
  notFoundTemplate: 'notFound',
  yieldTemplates: {
    navbar: {to: 'navbar'},
    footer: {to: 'footer'},
  }
});

// In the map, we set our routes.
Router.map(function () {
  // Index Route
  this.route('home', {
    path: '/',
    template: 'home',
    layoutTemplate: 'masterLayout'
  });
});

After you've saved the router, goto localhost:3000. You should see "Couldn't find a template named "masterLayout" or "masterLayout". Are you sure you defined it?" in red. That's ok.

Let's import materialize now. In the client folder, rename main.css to main.scss and paste in the following:

@import "{poetic:materialize-scss}/sass/components/_color.scss";
$primary-color: color("grey", "darken-4");
@import "{poetic:materialize-scss}/sass/materialize.scss";

You can change the entire color of the materialize theme by changing the primary-color to one of the colors from materialize's color panel.

Now we'll put together our template.

Create a layouts folder in your client folder.

Then create a masterLayout.html with the following:

<template name="masterLayout">
  {{> yield region='navbar'}}
    <main>
      {{> yield}}
    </main>
  {{> yield region='footer'}}
</template>

Visit localhost:3000 and you should now see:

Couldn't find a template named "navbar" or "navbar". Are you sure you defined it?
Couldn't find a template named "home" or "home". Are you sure you defined it?
Couldn't find a template named "footer" or "footer". Are you sure you defined it?

We're still good.

Let's over write our client/main.html with some new information. Paste in the following over what's there:

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <title>MeteoReddit</title>
</head>
<body>
</body>

This takes care of our material icon cdn, and our responsive viewport meta.

If you've done everything right, localhost:3000 should just be expecting our 3 templates right now.

Let's create them. First up is the navbar.

We need jQuery for materialize and we don't get document.ready because of the way blaze renders templates.

Create a new folder called controllers under client. I liked this naming convention because, while not necessarily controllers, they handle a lot of the data binding logic by way of calls.

In this folder, create a masterLayout.js and paste in the following:

Template.masterLayout.rendered = function () {
  $(".button-collapse").sideNav();
};

Now we can paste in the navbar with a working collapse.

In your layouts folder, create another folder called partials. In the new partials folder, create a navbar.html and paste in the following:

<template name="navbar">
  <nav>
    <div class="container">
      <div class="row">
        <div class="col s12">
          <div class="nav-wrapper">
            <a href="/" class="brand-logo">MeteoReddit</a>
            <a href="#" data-activates="mobile-nav" class="button-collapse"><i class="material-icons">menu</i></a>
            <ul class="right hide-on-med-and-down">
              {{#if currentUser}}
                <li><a href="/sign-out">Sign Out</a></li>
              {{else}}
                <li><a href="/sign-in">Sign In</a></li>
              {{/if}}
            </ul>
            <ul class="side-nav" id="mobile-nav">
              {{#if currentUser}}
                <li><a href="/sign-out">Sign Out</a></li>
              {{else}}
                <li><a href="/sign-in">Sign In</a></li>
              {{/if}}
            </ul>
          </div>
        </div>
      </div>
    </div>
  </nav>
</template>

At localhost:3000 you should have a functioning navbar.

2 errors to kill.

Let's get your footer setup.

create another partial in the layouts/partials folder called footer.html. Paste in the following:

<template name="footer">
  <footer class="page-footer">
    <div class="container">
      <div class="row">
        <div class="col l6 s12">
          <h5 class="white-text">Footer Content</h5>
          <p class="grey-text text-lighten-4">You can use rows and columns here to organize your footer content.</p>
        </div>
        <div class="col l4 offset-l2 s12">
          <h5 class="white-text">Links</h5>
          <ul>
            <li><a class="grey-text text-lighten-3" href="#!">Link 1</a></li>
            <li><a class="grey-text text-lighten-3" href="#!">Link 2</a></li>
            <li><a class="grey-text text-lighten-3" href="#!">Link 3</a></li>
            <li><a class="grey-text text-lighten-3" href="#!">Link 4</a></li>
          </ul>
        </div>
      </div>
    </div>
    <div class="footer-copyright">
      <div class="container">
      © 2016 Dan Vassallo
      <a class="grey-text text-lighten-4 right" href="#!">More Links</a>
      </div>
    </div>
  </footer>
</template>

You should now just see one error on localhost:3000 but The footer is up the navbar's butt. Let's fix that with a sticky footer from materialize.

In the client folder create a folder called stylesheets. In client/stylesheets create a _stickyfooter.scss partial and paste the following into it:

body {
  display: flex;
  min-height: 100vh;
  flex-direction: column;
}

main {
  flex: 1 0 auto;
}

Import this file into the client/main.scss file so it looks like this:

@import "./stylesheets/_stickyfooter.scss";

and visit localhost:3000 to see the magic happen.

Now last but not least, we need to create a homepage.

In the client folder, create another folder called views.

In views create another folder called pages.

In client/views/pages/ create a file called home.html and paste in the following:

<template name="home">
  <div class="container">
    <div class="row">
      <div class="col s12">
        <h1>Home</h1>
        <p>This is the index path.</p>
      </div>
    </div>
  </div>
</template>

That takes care of our home page errors -- even if we have some broken routes. Don't worry. That's up next.

###Remove boilerplate code

To get rid of the rest of the boilerplate code, setup CORS for your mobile devices, and gear up for the first admin, change the following:

Replace client/main.js with this

import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';

import './main.html';

And replace server/main.js with this

import { Meteor } from 'meteor/meteor';
import { BrowserPolicy } from 'meteor/browser-policy-common';

Meteor.startup(() => {
  BrowserPolicy.content.allowOriginForAll('*');
  // code to run on server at startup

  // create admin from settings
  if (Meteor.users.findOne(Meteor.settings.adminId)){
    Roles.addUsersToRoles(Meteor.settings.adminId, ['admin']);
  }
}

###Not Found and Authentication Routes

If we click the sign in link, we see an error in our view:

Couldn't find a template named "notFound" or "notFound". Are you sure you defined it?

This comes from our router here:

notFoundTemplate: 'notFound',

We've defined a "Not Found" or 404 template but haven't created one yet. Let's do this now.

Under client > views > pages create a file called notfound.html and put the following in it:

<template name="notFound">
  <main class="notfoundbody">
    <div class="container">
      <div class="row">
        <div class="col s12">
          <p>Page not found.</p>
        </div>
      </div>
    </div>
  </main>
</template>

When visit the sign-in link now, we should see "Page not found." instead. Our app is now rendering a custom 404 page.

Now we actually want to show a sign in page. We've already created the authentication methods and models by installing the account dependencies in the beginning. The specific version we've used already has materialize templates so they'll match our layout. Let's create the routes first, and then we'll do the templates.

Fix your router so it looks like this:

// In the map, we set our routes.
Router.map(function () {
  // Index Route
  this.route('home', {
    path: '/',
    template: 'home',
    layoutTemplate: 'masterLayout'
  });
  // Sign In Route
  AccountsTemplates.configureRoute('signIn', {
      name: 'signin',
      path: '/sign-in',
      template: 'signIn',
      layoutTemplate: 'masterLayout',
      redirect: '/',
  });
  // Sign Up Route
  AccountsTemplates.configureRoute('signUp', {
      name: 'sign-up',
      path: '/sign-up',
      template: 'signUp',
      layoutTemplate: 'masterLayout',
      redirect: '/',
  });
  // Sign Out Route
  this.route('/sign-out', function(){
      Meteor.logout(function(err) {
          if (err) alert('There was a problem logging you out.');
          Router.go("/");
      });
      Router.go("/");
  });
});

Now when we visit the browser we should no longer see our "Not Found" template but a new error:

Couldn't find a template named "signIn" or "signIn". Are you sure you defined it?

The templates are premade by the package I previously mentioned. So lets drop in a template for our sign in and sign up routes.

In client > views > pages let's create a file called signin.html. Put the following inside it:

<template name="signIn">
  <main class="signinbody">
    <div class="container">
      <div class="row row-pad">
        <div class="col s12 m6 offset-m3 l6 offset-l3 white z-depth-1 login">
          {{> atForm state='signIn'}}
        </div>
      </div>
    </div>
  </main>
</template>

Create another file called signup.html in the same folder and drop the following into it:

<template name="signUp">
  <main class="registerbody">
    <div class="container">
      <div class="row">
        <div class="col s12 m6 offset-m3 l6 offset-l3 white z-depth-1 login">
          {{> atForm state='signUp'}}
        </div>
      </div>
    </div>
  </main>
</template>

User authentication is literally finished. You can create a user, and it will sign you in. You should now see a "Log Out" link in the navbar. You can also click this to sign out.

###Authorization

We've also added a "materialized" admin account manager. Lets set that template up next.

Since we're loading a collection, we'll also need to setup our loader template.

First off let's update our router to include our "user management" route.

// In the map, we set our routes.
Router.map(function () {
  // Index Route
  this.route('home', {
    path: '/',
    template: 'home',
    layoutTemplate: 'masterLayout'
  });
  // User Mgmt Route
  this.route('usermgmt', {
    path: '/usermgmt',
    template: 'userManagement',
    layoutTemplate: 'masterLayout',
    onBeforeAction: function() {
      if (Meteor.loggingIn()) {
          this.render(this.loadingTemplate);
      } else if(!Roles.userIsInRole(Meteor.user(), ['admin'])) {
          this.redirect('/');
      }
      this.next();
    },
    loadingTemplate: 'loading'
  });
  // Sign In Route
  AccountsTemplates.configureRoute('signIn', {
      name: 'signin',
      path: '/sign-in',
      template: 'signIn',
      layoutTemplate: 'masterLayout',
      redirect: '/',
  });
  // Sign Up Route
  AccountsTemplates.configureRoute('signUp', {
      name: 'sign-up',
      path: '/sign-up',
      template: 'signUp',
      layoutTemplate: 'masterLayout',
      redirect: '/',
  });
  // Sign Out Route
  this.route('/sign-out', function(){
      Meteor.logout(function(err) {
          if (err) alert('There was a problem logging you out.');
          Router.go("/");
      });
      Router.go("/");
  });
});

Visit http://localhost:3000/usermgmt in your browser. You should get kicked back to the index because of the Role protection in the routes onBeforeAction. The route is missing a template or two, but lets get in to see the error first.

While logged in, paste the following into your console in chrome:

Meteor.userId();

Put the ID somewhere for a moment. Meteor will likely refresh your console so save it in an empty document.

In the root of your project, create a new file called settings.json. Put this in that file:

{
  "adminId": "<THE ID YOU JUST GOT FROM THE CHROME CONSOLE>"
}

Go to the terminal, and stop the meteor server with ctrl+c.

We're going to restart it with our settings file and take care of creating our first admin user -- Us.

Use the following command to start meteor:

meteor run --settings settings.json

Now when we visit localhost:3000/usermgmt we should see an error:

Couldn't find a template named "userManagement" or "userManagement". Are you sure you defined it?

Congrats, you now have user roles. Onto managing them. Let's use the prebuilt template for role management. Create an html file in client > views > pages called usermgmt.html. Put the following in it:

<template name="userManagement">
  <main class="usermgmtbody">
    <div class="container">
      <div class="row white z-depth-1 usermgmt">
        <div class="col s12 m10 offset-m1">
          {{> accountsAdmin}}
        </div>
      </div>
    </div>
  </main>
</template>

Now you should see the user management panel as an admin. Tight.

Let's make a helper so we can show a link to this page in the navbar when an admin is present. Under client > controllers create a file called navbar.js that matches the following:

Template.navbar.helpers({
  // check if user is an admin
  'isAdminUser': function() {
    return Roles.userIsInRole(Meteor.user(), ['admin']);
  }
});

And change client > layouts > partials > navbar.html to match this:

<template name="navbar">
  <nav>
    <div class="container">
      <div class="row">
        <div class="col s12">
          <div class="nav-wrapper">
            <a href="/" class="brand-logo">MeteoReddit</a>
            <a href="#" data-activates="mobile-nav" class="button-collapse"><i class="material-icons">menu</i></a>
            <ul class="right hide-on-med-and-down">
              {{#if isAdminUser}}
                <li><a href="/usermgmt">User Management</a></li>
              {{/if}}
              {{#if currentUser}}
                <li><a href="/sign-out">Sign Out</a></li>
              {{else}}
                <li><a href="/sign-in">Sign In</a></li>
              {{/if}}
            </ul>
            <ul class="side-nav" id="mobile-nav">
              {{#if currentUser}}
                <li><a href="/sign-out">Sign Out</a></li>
              {{else}}
                <li><a href="/sign-in">Sign In</a></li>
              {{/if}}
            </ul>
          </div>
        </div>
      </div>
    </div>
  </nav>
</template>

Sweet. We're done with user management. You can create any roles you want as the admin and assign them to any user.

Since we reference it, let's create a loading template using the loader from materialize.

Under client > views > pages create a file called loading.html and put the loader in it:

<template name="loading">
  <div class="loadingbody">
    <div class="preloader-wrapper big active">
      <div class="spinner-layer spinner-blue">
        <div class="circle-clipper left">
          <div class="circle"></div>
        </div><div class="gap-patch">
          <div class="circle"></div>
        </div><div class="circle-clipper right">
          <div class="circle"></div>
        </div>
      </div>

      <div class="spinner-layer spinner-red">
        <div class="circle-clipper left">
          <div class="circle"></div>
        </div><div class="gap-patch">
          <div class="circle"></div>
        </div><div class="circle-clipper right">
          <div class="circle"></div>
        </div>
      </div>

      <div class="spinner-layer spinner-yellow">
        <div class="circle-clipper left">
          <div class="circle"></div>
        </div><div class="gap-patch">
          <div class="circle"></div>
        </div><div class="circle-clipper right">
          <div class="circle"></div>
        </div>
      </div>

      <div class="spinner-layer spinner-green">
        <div class="circle-clipper left">
          <div class="circle"></div>
        </div><div class="gap-patch">
          <div class="circle"></div>
        </div><div class="circle-clipper right">
          <div class="circle"></div>
        </div>
      </div>
    </div>
  </div>
</template>

Let's make a route to see the loader. Fix your router to match the following and visit localhost:3000/loading when you're finished:

// In the configuration, we declare the layout, 404, loading,
// navbar, and footer templates.
Router.configure({
  layoutTemplate: 'masterLayout',
  loadingTemplate: 'loading',
  notFoundTemplate: 'notFound',
  yieldTemplates: {
    navbar: {to: 'navbar'},
    footer: {to: 'footer'},
  }
});

// In the map, we set our routes.
Router.map(function () {
  // Index Route
  this.route('home', {
    path: '/',
    template: 'home',
    layoutTemplate: 'masterLayout'
  });
  this.route('loading', {
    path: 'loading',
    template: 'loading',
    layoutTemplate: 'masterLayout'
  });
  // User Mgmt Route
  this.route('usermgmt', {
    path: '/usermgmt',
    template: 'userManagement',
    layoutTemplate: 'masterLayout',
    onBeforeAction: function() {
      if (Meteor.loggingIn()) {
          this.render(this.loadingTemplate);
      } else if(!Roles.userIsInRole(Meteor.user(), ['admin'])) {
          this.redirect('/');
      }
      this.next();
    },
    loadingTemplate: 'loading'
  });
  // Sign In Route
  AccountsTemplates.configureRoute('signIn', {
      name: 'signin',
      path: '/sign-in',
      template: 'signIn',
      layoutTemplate: 'masterLayout',
      redirect: '/',
  });
  // Sign Up Route
  AccountsTemplates.configureRoute('signUp', {
      name: 'sign-up',
      path: '/sign-up',
      template: 'signUp',
      layoutTemplate: 'masterLayout',
      redirect: '/',
  });
  // Sign Out Route
  this.route('/sign-out', function(){
      Meteor.logout(function(err) {
          if (err) alert('There was a problem logging you out.');
          Router.go("/");
      });
      Router.go("/");
  });
});

The loader looks a little screwy and our margins from the navbar are a bit funky. Let's write some sass to fix this.

Change your client > main.scss to match the following:

@import "{poetic:materialize-scss}/sass/components/_color.scss";
$primary-color: color("grey", "darken-4");
@import "{poetic:materialize-scss}/sass/materialize.scss";
@import "./stylesheets/_stickyfooter.scss";
@import "./stylesheets/_loading.scss";
@import "./stylesheets/_navbar.scss";

Now create the two new files we want to import.

In client > stylesheets create a file called _loading.scss with the following in it:

main{
  position: relative;
}

.loadingbody{
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 1 0 auto;
  position: absolute;
  left: 0;
  bottom: 0;
  top: 0;
  right: 0;
  z-index: -1;
}

In the same folder, make another file called _navbar.scss with the following in it:

nav{
  margin-bottom: 40px;
}

We now have the entire foundation to build any authorized, authenticated, CRUD app with meteor. Congrats. Next, we'll be adding our own functionality, deploying the app, and compiling it for our phone.