diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000..c71d366
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,4 @@
+{
+ "plugins": ["transform-runtime"],
+ "presets": ["es2015", "stage-0"]
+}
diff --git a/.gitignore b/.gitignore
index 8320361..c828994 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
/nbproject/
/node_modules/
-/dist/
+/build/
+!/build/jquery-1.6.2.min.js
+!/build/jquery.event.drag-2.0.js
\ No newline at end of file
diff --git a/app.js b/app.js
index 2a5b35f..76eb28b 100644
--- a/app.js
+++ b/app.js
@@ -13,7 +13,7 @@ const app = express();
const server = http.createServer(app);
const io = require('socket.io').listen(server);
-app.set('views', path.join(__dirname, 'views'));
+//app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
server.listen(8085, function () {
@@ -27,13 +27,14 @@ app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(cookieParser());
-app.use(express.static(path.join(__dirname, 'public')));
+//app.use(express.static(path.join(__dirname, 'public')));
+//app.use(express.static('./views/'));
+//app.use('/*', express.static('./views/index.ejs'));
app.use('/', routes);
app.use('/users', users);
-
io.on('connection', function (socket) {
socket.emit('test', 'Connectedddd');
socket.on('drawClick', function (data) {
diff --git a/gulpfile.babel.js b/gulpfile.babel.js
new file mode 100644
index 0000000..eb8ae7e
--- /dev/null
+++ b/gulpfile.babel.js
@@ -0,0 +1,112 @@
+'use strict';
+import gulp from 'gulp';
+import nodemon from 'gulp-nodemon';
+import browserSync from 'browser-sync';
+import del from 'del';
+import source from 'vinyl-source-stream';
+import browserify from 'browserify';
+import babelify from 'babelify';
+import ngAnnotate from 'browserify-ngannotate';
+import templateCache from 'gulp-angular-templatecache';
+import rename from 'gulp-rename';
+import notify from 'gulp-notify';
+import ejs from 'gulp-ejs';
+import htmlreplace from 'gulp-html-replace';
+import concat from 'gulp-concat';
+import minifyCSS from 'gulp-minify-css';
+
+// Where our files are located
+var jsFiles = "public/assets/javascripts/*.js";
+var viewFiles = "views/*.ejs";
+
+var interceptErrors = function (error) {
+ var args = Array.prototype.slice.call(arguments);
+
+ // Send error to notification center with gulp-notify
+ notify.onError({
+ title: 'Compile Error',
+ message: '<%= error.message %>'
+ }).apply(this, args);
+
+ // Keep gulp from hanging on this task
+ this.emit('end');
+};
+
+gulp.task('default', ['build', 'browser-sync'], function () {
+ gulp.watch(['./views/**.**', './public/**/*'], ['build']);
+ gulp.watch('./build/', browserSync.reload());
+});
+
+gulp.task('browser-sync', ['nodemon'], function () {
+ browserSync.init(['./build/**.**'], {
+ port: 8080,
+ serveStatic: ['./build/'],
+ proxy: {
+ target: 'localhost:8085', // original port
+ ws: true // enables websockets
+ }
+ });
+});
+
+gulp.task('nodemon', (cb) => {
+ var started = false;
+
+ return nodemon({
+ script: 'app.js'
+ }).on('start', function () {
+ if (!started) {
+ cb();
+ started = true;
+ }
+ });
+});
+
+gulp.task('build', ['browserify', 'css'], () => {
+ gulp.start('html');
+ //gulp.start() will have to be replaced by gulp.series() when gulp 4.0 is released.
+});
+
+gulp.task('browserify', ['views'], function () {
+ return browserify(['./public/assets/javascripts/main.js'])
+ .transform(babelify, {presets: ["es2015"]})
+ .transform(ngAnnotate)
+ .bundle()
+ .on('error', interceptErrors)
+ .pipe(source('main.js'))
+ .pipe(gulp.dest('./build/'));
+});
+
+gulp.task('views', function () {
+ return gulp.src(viewFiles)
+ .pipe(templateCache({
+ standalone: true
+ }))
+ .on('error', interceptErrors)
+ .pipe(rename("app.templates.js"))
+ .pipe(gulp.dest('./public/config/'));
+});
+
+gulp.task('css', function () {
+ gulp.src('./public/assets/stylesheets/*.css')
+ .pipe(minifyCSS())
+ .pipe(concat('style.css'))
+ .on('error', interceptErrors)
+ .pipe(gulp.dest('./build'))
+});
+
+gulp.task('html', function () {
+ return gulp.src("./views/index.ejs")
+ .pipe(ejs({}, {ext: '.html'}))
+ .pipe(htmlreplace({
+ css: 'style.css',
+ js: 'main.js'
+ }))
+ .on('error', interceptErrors)
+ .pipe(gulp.dest('./build/'));
+});
+
+gulp.task('clean', () => {
+ del(['./build/**', '!./build', '!./build/jquery-1.6.2.min.js', '!./build/jquery.event.drag-2.0.js', '!./build/socket.io.js']).then(paths => {
+ console.log('Deleted files and folders:\n', paths.join('\n'));
+ });
+});
diff --git a/gulpfile.js b/gulpfile.js
deleted file mode 100644
index d10fec5..0000000
--- a/gulpfile.js
+++ /dev/null
@@ -1,35 +0,0 @@
-'use strict';
-
-const gulp = require('gulp'),
- gulpLoadPlugins = require('gulp-load-plugins'),
- plugins = gulpLoadPlugins(),
- del = require('del'),
- eslint = require('gulp-eslint');
-
-gulp.task('scripts', function() {
- return gulp.src('public/javascripts/*.js')
- .pipe(plugins.concat('main.js'))
- .pipe(gulp.dest('dist/assets/js'))
- console.log("Scripts task complete");
-});
-
-gulp.task('clean', function() {
- return del(['dist/assets/css', 'dist/assets/js', 'dist/assets/img']);
-});
-
-gulp.task('default', ['clean', 'lint'], function() {
- gulp.start('scripts');
-});
-
-gulp.task('watch', function() {
- //gulp.watch('public/stylesheets/**/*.css', ['styles']);
- gulp.watch('public/javascripts/*.js', ['scripts']);
- plugins.livereload.listen();
- gulp.watch(['./**']).on('change', plugins.livereload.changed);
-});
-
-gulp.task('lint', () => {
- return gulp.src(['**/*.js','!node_modules/**'])
- .pipe(eslint('.eslintrc.json'))
- .pipe(eslint.formatEach());
-});
\ No newline at end of file
diff --git a/package.json b/package.json
index 16a6eb6..5020b92 100644
--- a/package.json
+++ b/package.json
@@ -18,23 +18,40 @@
},
"homepage": "https://github.com/gevalo1/WebApps3#readme",
"devDependencies": {
+ "babel-core": "^6.18.2",
+ "babel-loader": "^6.2.7",
+ "babel-plugin-transform-runtime": "^6.15.0",
+ "babel-polyfill": "^6.16.0",
+ "babel-preset-es2015": "^6.18.0",
+ "babel-preset-stage-0": "^6.16.0",
+ "babelify": "^7.3.0",
+ "browser-sync": "^2.17.5",
+ "browserify": "^13.1.1",
+ "browserify-ngannotate": "^2.0.0",
"del": "^2.2.2",
"eslint": "^3.9.0",
"eslint-config-standard": "^6.2.1",
"eslint-plugin-promise": "^3.3.0",
"eslint-plugin-standard": "^2.0.1",
"gulp": "^3.9.1",
- "gulp-autoprefixer": "^3.1.1",
+ "gulp-angular-templatecache": "^2.0.0",
"gulp-concat": "^2.6.0",
- "gulp-cssnano": "^2.1.2",
- "gulp-livereload": "^3.8.1",
- "gulp-load-plugins": "^1.3.0",
- "gulp-rename": "^1.2.2"
+ "gulp-ejs": "^2.2.1",
+ "gulp-html-replace": "^1.6.1",
+ "gulp-minify-css": "^1.2.4",
+ "gulp-nodemon": "^2.2.1",
+ "gulp-notify": "^2.2.0",
+ "gulp-rename": "^1.2.2",
+ "gulp-uglify": "^2.0.0",
+ "vinyl-source-stream": "^1.1.0"
},
"dependencies": {
- "ejs": "2.5.2",
+ "angular": "^1.5.8",
+ "angular-ui-router": "^0.3.1",
+ "babel-runtime": "^6.18.0",
"body-parser": "1.15.2",
"cookie-parser": "1.4.3",
+ "ejs": "2.5.2",
"express": "4.14.0",
"morgan": "1.7.0",
"serve-favicon": "2.3.0",
diff --git a/public/app.js b/public/app.js
new file mode 100644
index 0000000..9a44a0c
--- /dev/null
+++ b/public/app.js
@@ -0,0 +1,34 @@
+import angular from 'angular';
+
+// Import our app config files
+import constants from './config/app.constants';
+import appConfig from './config/app.config';
+import appRun from './config/app.run';
+import 'angular-ui-router';
+// Import our templates file (generated by Gulp)
+import './config/app.templates';
+// Import our app functionaity
+import './components';
+import './services';
+import './auth';
+
+
+// Create and bootstrap application
+const requires = [
+ 'app.components',
+ 'app.services',
+ 'app.auth',
+];
+
+// Mount on window for testing
+window.app = angular.module('app', requires);
+
+angular.module('app').constant('AppConstants', constants);
+
+angular.module('app').config(appConfig);
+
+angular.module('app').run(appRun);
+
+angular.bootstrap(document, ['app'], {
+ strictDi: true
+});
diff --git a/public/app/auth/auth.config.js b/public/app/auth/auth.config.js
new file mode 100644
index 0000000..04f2d25
--- /dev/null
+++ b/public/app/auth/auth.config.js
@@ -0,0 +1,33 @@
+function AuthConfig($stateProvider, $httpProvider) {
+ 'ngInject';
+
+ // Define the routes
+ $stateProvider
+
+ .state('app.login', {
+ url: '/login',
+ controller: 'AuthCtrl as $ctrl',
+ templateUrl: 'auth/auth.html',
+ title: 'Sign in',
+ resolve: {
+ auth: function(User) {
+ return User.ensureAuthIs(false);
+ }
+ }
+ })
+
+ .state('app.register', {
+ url: '/register',
+ controller: 'AuthCtrl as $ctrl',
+ templateUrl: 'auth/auth.html',
+ title: 'Sign up',
+ resolve: {
+ auth: function(User) {
+ return User.ensureAuthIs(false);
+ }
+ }
+ });
+
+};
+
+export default AuthConfig;
\ No newline at end of file
diff --git a/public/app/auth/auth.controller.js b/public/app/auth/auth.controller.js
new file mode 100644
index 0000000..bcf974b
--- /dev/null
+++ b/public/app/auth/auth.controller.js
@@ -0,0 +1,27 @@
+class AuthCtrl {
+ constructor(User, $state) {
+ 'ngInject';
+
+ this._User = User;
+ this._$state = $state;
+
+ this.title = $state.current.title;
+ this.authType = $state.current.name.replace('app.', '');
+ }
+
+ submitForm() {
+ this.isSubmitting = true;
+
+ this._User.attemptAuth(this.authType, this.formData).then(
+ (res) => {
+ this._$state.go('app.home');
+ },
+ (err) => {
+ this.isSubmitting = false;
+ this.errors = err.data.errors;
+ }
+ );
+ }
+}
+
+export default AuthCtrl;
\ No newline at end of file
diff --git a/public/app/auth/auth.html b/public/app/auth/auth.html
new file mode 100644
index 0000000..aafdd8e
--- /dev/null
+++ b/public/app/auth/auth.html
@@ -0,0 +1,52 @@
+
\ No newline at end of file
diff --git a/public/app/auth/index.js b/public/app/auth/index.js
new file mode 100644
index 0000000..5838d03
--- /dev/null
+++ b/public/app/auth/index.js
@@ -0,0 +1,15 @@
+import angular from 'angular';
+
+// Create the home module where our functionality can attach to
+let authModule = angular.module('app.auth', []);
+
+
+// Include our UI-Router config settings
+import AuthConfig from './auth.config';
+authModule.config(AuthConfig);
+
+// Controllers
+import AuthCtrl from './auth.controller';
+authModule.controller('AuthCtrl', AuthCtrl);
+
+export default authModule;
\ No newline at end of file
diff --git a/public/app/components/article-helpers/article-list.component.js b/public/app/components/article-helpers/article-list.component.js
new file mode 100644
index 0000000..1a79a6a
--- /dev/null
+++ b/public/app/components/article-helpers/article-list.component.js
@@ -0,0 +1,68 @@
+class ArticleListCtrl {
+ constructor(Articles, $scope) {
+ 'ngInject';
+
+ this._Articles = Articles;
+
+ this.setListTo(this.listConfig);
+
+ $scope.$on('setListTo', (ev, newList) => {
+ this.setListTo(newList);
+ });
+
+ $scope.$on('setPageTo', (ev, pageNumber) => {
+ this.setPageTo(pageNumber);
+ })
+ }
+
+ setListTo(newList) {
+ this.list = [];
+
+ this.listConfig = newList;
+
+ this.runQuery();
+ }
+
+ setPageTo(pageNumber) {
+ this.listConfig.currentPage = pageNumber;
+
+ this.runQuery();
+ }
+
+ runQuery() {
+ this.loading = true;
+
+ let queryConfig = {
+ type: this.listConfig.type,
+ filters: this.listConfig.filters || {}
+ };
+
+ queryConfig.filters.limit = this.limit;
+
+ if (!this.listConfig.currentPage) {
+ this.listConfig.currentPage = 1;
+ }
+
+ queryConfig.filters.offset = (this.limit * (this.listConfig.currentPage - 1));
+
+ this._Articles.query(queryConfig).then(
+ (res) => {
+ this.loading = false;
+ this.list = res.articles;
+
+ this.listConfig.totalPages = Math.ceil(res.articlesCount / this.limit);
+ }
+ );
+ }
+}
+
+let ArticleList = {
+ bindings: {
+ limit: '=',
+ listConfig: '='
+ },
+ controller: ArticleListCtrl,
+ templateUrl: 'components/article-helpers/article-list.html'
+};
+
+export default ArticleList;
\ No newline at end of file
diff --git a/public/app/components/article-helpers/article-list.html b/public/app/components/article-helpers/article-list.html
new file mode 100644
index 0000000..1f82eed
--- /dev/null
+++ b/public/app/components/article-helpers/article-list.html
@@ -0,0 +1,20 @@
+
+
+
+
+ Loading articles...
+
+
+
+ No articles are here... yet.
+
+
+
+
\ No newline at end of file
diff --git a/public/app/components/article-helpers/article-meta.component.js b/public/app/components/article-helpers/article-meta.component.js
new file mode 100644
index 0000000..1dca631
--- /dev/null
+++ b/public/app/components/article-helpers/article-meta.component.js
@@ -0,0 +1,9 @@
+let ArticleMeta = {
+ bindings: {
+ article: '='
+ },
+ transclude: true,
+ templateUrl: 'components/article-helpers/article-meta.html'
+};
+
+export default ArticleMeta;
\ No newline at end of file
diff --git a/public/app/components/article-helpers/article-meta.html b/public/app/components/article-helpers/article-meta.html
new file mode 100644
index 0000000..ccc3702
--- /dev/null
+++ b/public/app/components/article-helpers/article-meta.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/app/components/article-helpers/article-preview.component.js b/public/app/components/article-helpers/article-preview.component.js
new file mode 100644
index 0000000..6a36190
--- /dev/null
+++ b/public/app/components/article-helpers/article-preview.component.js
@@ -0,0 +1,8 @@
+let ArticlePreview = {
+ bindings: {
+ article: '='
+ },
+ templateUrl: 'components/article-helpers/article-preview.html'
+};
+
+export default ArticlePreview;
\ No newline at end of file
diff --git a/public/app/components/article-helpers/article-preview.html b/public/app/components/article-helpers/article-preview.html
new file mode 100644
index 0000000..3bc6d0b
--- /dev/null
+++ b/public/app/components/article-helpers/article-preview.html
@@ -0,0 +1,21 @@
+
+
+
+ {{$ctrl.article.favoritesCount}}
+
+
+
+
+
+
+ Read more...
+
+
+
\ No newline at end of file
diff --git a/public/app/components/article-helpers/list-pagination.component.js b/public/app/components/article-helpers/list-pagination.component.js
new file mode 100644
index 0000000..37eea7f
--- /dev/null
+++ b/public/app/components/article-helpers/list-pagination.component.js
@@ -0,0 +1,33 @@
+class ListPaginationCtrl {
+ constructor($scope) {
+ 'ngInject';
+
+ this._$scope = $scope;
+
+ }
+
+ pageRange(total) {
+ let pages = [];
+
+ for (var i = 0; i < total; i++) {
+ pages.push(i + 1)
+ }
+
+ return pages;
+ }
+
+ changePage(number) {
+ this._$scope.$emit('setPageTo', number);
+ }
+}
+
+let ListPagination= {
+ bindings: {
+ totalPages: '=',
+ currentPage: '='
+ },
+ controller: ListPaginationCtrl,
+ templateUrl: 'components/article-helpers/list-pagination.html'
+};
+
+export default ListPagination;
\ No newline at end of file
diff --git a/public/app/components/article-helpers/list-pagination.html b/public/app/components/article-helpers/list-pagination.html
new file mode 100644
index 0000000..39ebf6c
--- /dev/null
+++ b/public/app/components/article-helpers/list-pagination.html
@@ -0,0 +1,14 @@
+
\ No newline at end of file
diff --git a/public/app/components/buttons/favorite-btn.component.js b/public/app/components/buttons/favorite-btn.component.js
new file mode 100644
index 0000000..0dbc72a
--- /dev/null
+++ b/public/app/components/buttons/favorite-btn.component.js
@@ -0,0 +1,47 @@
+class FavoriteBtnCtrl {
+ constructor(User, Articles, $state) {
+ 'ngInject';
+
+ this._Articles = Articles;
+ this._User = User;
+ this._$state = $state;
+ }
+
+ submit() {
+ this.isSubmitting = true;
+
+ if (!this._User.current) {
+ this._$state.go('app.register');
+ return;
+ }
+
+ if (this.article.favorited) {
+ this._Articles.unfavorite(this.article.slug).then(
+ () => {
+ this.isSubmitting = false;
+ this.article.favorited = false;
+ this.article.favoritesCount--;
+ }
+ )
+ } else {
+ this._Articles.favorite(this.article.slug).then(
+ () => {
+ this.isSubmitting = false;
+ this.article.favorited = true;
+ this.article.favoritesCount++;
+ }
+ )
+ }
+ }
+}
+
+let FavoriteBtn = {
+ bindings: {
+ article: '='
+ },
+ transclude: true,
+ controller: FavoriteBtnCtrl,
+ templateUrl: 'components/buttons/favorite-btn.html'
+};
+
+export default FavoriteBtn;
\ No newline at end of file
diff --git a/public/app/components/buttons/favorite-btn.html b/public/app/components/buttons/favorite-btn.html
new file mode 100644
index 0000000..9fd3f9f
--- /dev/null
+++ b/public/app/components/buttons/favorite-btn.html
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/public/app/components/buttons/follow-btn.component.js b/public/app/components/buttons/follow-btn.component.js
new file mode 100644
index 0000000..61e69c4
--- /dev/null
+++ b/public/app/components/buttons/follow-btn.component.js
@@ -0,0 +1,45 @@
+class FollowBtnCtrl {
+ constructor(Profile, User, $state) {
+ 'ngInject';
+
+ this._Profile = Profile;
+ this._User = User;
+
+ this._$state = $state;
+ }
+
+ submit() {
+ this.isSubmitting = true;
+
+ if (!this._User.current) {
+ this._$state.go('app.register');
+ return;
+ }
+
+ if (this.user.following) {
+ this._Profile.unfollow(this.user.username).then(
+ () => {
+ this.isSubmitting = false;
+ this.user.following = false;
+ }
+ )
+ } else {
+ this._Profile.follow(this.user.username).then(
+ () => {
+ this.isSubmitting = false;
+ this.user.following = true;
+ }
+ )
+ }
+ }
+}
+
+let FollowBtn = {
+ bindings: {
+ user: '='
+ },
+ controller: FollowBtnCtrl,
+ templateUrl: 'components/buttons/follow-btn.html'
+};
+
+export default FollowBtn;
\ No newline at end of file
diff --git a/public/app/components/buttons/follow-btn.html b/public/app/components/buttons/follow-btn.html
new file mode 100644
index 0000000..dc39ece
--- /dev/null
+++ b/public/app/components/buttons/follow-btn.html
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/public/app/components/index.js b/public/app/components/index.js
new file mode 100644
index 0000000..4be029a
--- /dev/null
+++ b/public/app/components/index.js
@@ -0,0 +1,29 @@
+import angular from 'angular';
+
+let componentsModule = angular.module('app.components', []);
+
+import ListErrors from './list-errors.component';
+componentsModule.component('listErrors', ListErrors);
+
+import ShowAuthed from './show-authed.directive';
+componentsModule.directive('showAuthed', ShowAuthed);
+
+import FollowBtn from './buttons/follow-btn.component';
+componentsModule.component('followBtn', FollowBtn);
+
+import ArticleMeta from './article-helpers/article-meta.component';
+componentsModule.component('articleMeta', ArticleMeta);
+
+import FavoriteBtn from './buttons/favorite-btn.component';
+componentsModule.component('favoriteBtn', FavoriteBtn);
+
+import ArticlePreview from './article-helpers/article-preview.component';
+componentsModule.component('articlePreview', ArticlePreview);
+
+import ArticleList from './article-helpers/article-list.component';
+componentsModule.component('articleList', ArticleList);
+
+import ListPagination from './article-helpers/list-pagination.component';
+componentsModule.component('listPagination', ListPagination);
+
+export default componentsModule;
diff --git a/public/app/components/list-errors.component.js b/public/app/components/list-errors.component.js
new file mode 100644
index 0000000..2de72c9
--- /dev/null
+++ b/public/app/components/list-errors.component.js
@@ -0,0 +1,8 @@
+let ListErrors = {
+ bindings: {
+ errors: '='
+ },
+ templateUrl: 'components/list-errors.html'
+};
+
+export default ListErrors;
\ No newline at end of file
diff --git a/public/app/components/list-errors.html b/public/app/components/list-errors.html
new file mode 100644
index 0000000..5858508
--- /dev/null
+++ b/public/app/components/list-errors.html
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/public/app/components/show-authed.directive.js b/public/app/components/show-authed.directive.js
new file mode 100644
index 0000000..fa18c1f
--- /dev/null
+++ b/public/app/components/show-authed.directive.js
@@ -0,0 +1,28 @@
+function ShowAuthed(User) {
+ 'ngInject';
+
+ return {
+ restrict: 'A',
+ link: function(scope, element, attrs) {
+ scope.User = User;
+
+ scope.$watch('User.current', function(val){
+ if (val) {
+ if (attrs.showAuthed === 'true') {
+ element.css({ display: 'inherit' })
+ } else {
+ element.css({ display: 'none' })
+ }
+ } else {
+ if (attrs.showAuthed === 'true') {
+ element.css({ display: 'none' })
+ } else {
+ element.css({ display: 'inherit' })
+ }
+ }
+ });
+ }
+ };
+}
+
+export default ShowAuthed;
\ No newline at end of file
diff --git a/public/app/config/app.config.js b/public/app/config/app.config.js
new file mode 100644
index 0000000..d33d848
--- /dev/null
+++ b/public/app/config/app.config.js
@@ -0,0 +1,29 @@
+import authInterceptor from './auth.interceptor';
+
+function AppConfig($httpProvider, $stateProvider, $locationProvider, $urlRouterProvider) {
+ 'ngInject';
+
+ $httpProvider.interceptors.push(authInterceptor);
+
+ /*
+ If you don't want hashbang routing, uncomment this line.
+ Our tutorial will be using hashbang routing though :)
+ */
+ // $locationProvider.html5Mode(true);
+
+ $stateProvider
+ .state('app', {
+ abstract: true,
+ templateUrl: 'layout/app-view.html',
+ resolve: {
+ auth: function(User) {
+ return User.verifyAuth();
+ }
+ }
+ });
+
+ $urlRouterProvider.otherwise('/');
+
+}
+
+export default AppConfig;
diff --git a/public/app/config/app.constants.js b/public/app/config/app.constants.js
new file mode 100644
index 0000000..7ac5097
--- /dev/null
+++ b/public/app/config/app.constants.js
@@ -0,0 +1,8 @@
+const AppConstants = {
+ //api: 'http://localhost:3000/api',
+ api: 'https://conduit.productionready.io/api',
+ jwtKey: 'jwtToken',
+ appName: 'Conduit',
+};
+
+export default AppConstants;
diff --git a/public/app/config/app.run.js b/public/app/config/app.run.js
new file mode 100644
index 0000000..9e711f4
--- /dev/null
+++ b/public/app/config/app.run.js
@@ -0,0 +1,21 @@
+function AppRun(AppConstants, $rootScope) {
+ 'ngInject';
+
+ // change page title based on state
+ $rootScope.$on('$stateChangeSuccess', (event, toState) => {
+ $rootScope.setPageTitle(toState.title);
+ });
+
+ // Helper method for setting the page's title
+ $rootScope.setPageTitle = (title) => {
+ $rootScope.pageTitle = '';
+ if (title) {
+ $rootScope.pageTitle += title;
+ $rootScope.pageTitle += ' \u2014 ';
+ }
+ $rootScope.pageTitle += AppConstants.appName;
+ };
+
+}
+
+export default AppRun;
diff --git a/public/app/config/app.templates.js b/public/app/config/app.templates.js
new file mode 100644
index 0000000..e1bb4bb
--- /dev/null
+++ b/public/app/config/app.templates.js
@@ -0,0 +1,2 @@
+angular.module('templates', []).run(['$templateCache', function($templateCache) {$templateCache.put('error.ejs','<%= message %>
\n<%= error.status %>
\n<%= error.stack %>
\n');
+$templateCache.put('index.ejs','\r\n\r\n \r\n test\r\n\t\r\n \r\n \r\n\t\r\n \r\n \r\n \r\n
\r\n \r\n
\r\n
\r\n \r\n
\r\n\t\r\n \r\n \r\n \r\n\t\r\n \r\n\t\r\n \r\n\r\n');}]);
\ No newline at end of file
diff --git a/public/app/config/auth.interceptor.js b/public/app/config/auth.interceptor.js
new file mode 100644
index 0000000..478c893
--- /dev/null
+++ b/public/app/config/auth.interceptor.js
@@ -0,0 +1,21 @@
+function authInterceptor(JWT, AppConstants, $window, $q) {
+ 'ngInject';
+
+ return {
+ request: function(config) {
+ if (config.url.indexOf(AppConstants.api) === 0 && JWT.get()) {
+ config.headers.Authorization = 'Token ' + JWT.get();
+ }
+ return config;
+ },
+ responseError: function(rejection) {
+ if (rejection.status === 401) {
+ JWT.destroy();
+ $window.location.reload();
+ }
+ return $q.reject(rejection);
+ }
+ }
+}
+
+export default authInterceptor;
\ No newline at end of file
diff --git a/public/app/services/articles.service.js b/public/app/services/articles.service.js
new file mode 100644
index 0000000..d765c37
--- /dev/null
+++ b/public/app/services/articles.service.js
@@ -0,0 +1,75 @@
+export default class Articles {
+ constructor(AppConstants, $http, $q) {
+ 'ngInject';
+
+ this._AppConstants = AppConstants;
+ this._$http = $http;
+ this._$q = $q;
+ }
+
+ save(article) {
+ let request = {};
+
+ if (article.slug) {
+ request.url = `${this._AppConstants.api}/articles/${article.slug}`;
+ request.method = 'PUT';
+ delete article.slug;
+ } else {
+ request.url = `${this._AppConstants.api}/articles`;
+ request.method = 'POST';
+ }
+
+ request.data = { article: article };
+
+ return this._$http(request).then((res) => res.data.article);
+ }
+
+ get(slug) {
+ let deferred = this._$q.defer();
+
+ if (!slug.replace(" ", "")) {
+ deferred.reject("Article slug is empty");
+ return deferred.promise;
+ }
+
+ this._$http({
+ url: this._AppConstants.api + '/articles/' + slug,
+ method: 'GET'
+ }).then(
+ (res) => deferred.resolve(res.data.article),
+ (err) => deferred.reject(err)
+ );
+
+ return deferred.promise;
+ }
+
+ destroy(slug) {
+ return this._$http({
+ url: this._AppConstants.api + '/articles/' + slug,
+ method: 'DELETE'
+ });
+ }
+
+ favorite(slug) {
+ return this._$http({
+ url: this._AppConstants.api + '/articles/' + slug + '/favorite',
+ method: 'POST'
+ });
+ }
+
+ unfavorite(slug) {
+ return this._$http({
+ url: this._AppConstants.api + '/articles/' + slug + '/favorite',
+ method: 'DELETE'
+ });
+ }
+
+ query(config) {
+ let request = {
+ url: this._AppConstants.api + '/articles' + ((config.type === 'feed') ? '/feed' : ''),
+ method: 'GET',
+ params: config.filters ? config.filters : null
+ };
+ return this._$http(request).then((res) => res.data);
+ }
+}
\ No newline at end of file
diff --git a/public/app/services/comments.service.js b/public/app/services/comments.service.js
new file mode 100644
index 0000000..5f9c310
--- /dev/null
+++ b/public/app/services/comments.service.js
@@ -0,0 +1,30 @@
+export default class Comments {
+ constructor(AppConstants, $http) {
+ 'ngInject';
+
+ this._AppConstants = AppConstants;
+ this._$http = $http;
+ }
+
+ add(slug, payload) {
+ return this._$http({
+ url: `${this._AppConstants.api}/articles/${slug}/comments`,
+ method: 'POST',
+ data: { comment: { body: payload } }
+ }).then((res) => res.data.comment);
+ }
+
+ destroy(commentId, articleSlug) {
+ return this._$http({
+ url: `${this._AppConstants.api}/articles/${articleSlug}/comments/${commentId}`,
+ method: 'DELETE'
+ });
+ }
+
+ getAll(slug) {
+ return this._$http({
+ url: `${this._AppConstants.api}/articles/${slug}/comments`,
+ method: 'GET'
+ }).then((res) => res.data.comments);
+ }
+}
\ No newline at end of file
diff --git a/public/app/services/index.js b/public/app/services/index.js
new file mode 100644
index 0000000..f69d5c2
--- /dev/null
+++ b/public/app/services/index.js
@@ -0,0 +1,24 @@
+import angular from 'angular';
+
+// Create the module where our functionality can attach to
+let servicesModule = angular.module('app.services', []);
+
+import UserService from './user.service';
+servicesModule.service('User', UserService);
+
+import JwtService from './jwt.service';
+servicesModule.service('JWT', JwtService);
+
+import ProfileService from './profile.service';
+servicesModule.service('Profile', ProfileService);
+
+import ArticlesService from './articles.service';
+servicesModule.service('Articles', ArticlesService);
+
+import CommentsService from './comments.service';
+servicesModule.service('Comments', CommentsService);
+
+import TagsService from './tags.service';
+servicesModule.service('Tags', TagsService);
+
+export default servicesModule;
diff --git a/public/app/services/jwt.service.js b/public/app/services/jwt.service.js
new file mode 100644
index 0000000..a6ac405
--- /dev/null
+++ b/public/app/services/jwt.service.js
@@ -0,0 +1,20 @@
+export default class JWT {
+ constructor(AppConstants, $window) {
+ 'ngInject';
+
+ this._AppConstants = AppConstants;
+ this._$window = $window;
+ }
+
+ save(token) {
+ this._$window.localStorage[this._AppConstants.jwtKey] = token;
+ }
+
+ get() {
+ return this._$window.localStorage[this._AppConstants.jwtKey];
+ }
+
+ destroy() {
+ this._$window.localStorage.removeItem(this._AppConstants.jwtKey);
+ }
+}
\ No newline at end of file
diff --git a/public/app/services/profile.service.js b/public/app/services/profile.service.js
new file mode 100644
index 0000000..c11fcb9
--- /dev/null
+++ b/public/app/services/profile.service.js
@@ -0,0 +1,31 @@
+export default class Profile {
+ constructor(AppConstants, $http) {
+ 'ngInject';
+
+ this._AppConstants = AppConstants;
+ this._$http = $http;
+ }
+
+ get(username) {
+ return this._$http({
+ url: this._AppConstants.api + '/profiles/' + username,
+ method: 'GET'
+ }).then(
+ (res) => res.data.profile
+ );
+ }
+
+ follow(username) {
+ return this._$http({
+ url: this._AppConstants.api + '/profiles/' + username + '/follow',
+ method: 'POST'
+ }).then((res) => res.data);
+ }
+
+ unfollow(username) {
+ return this._$http({
+ url: this._AppConstants.api + '/profiles/' + username + '/follow',
+ method: 'DELETE'
+ }).then((res) => res.data);
+ }
+}
\ No newline at end of file
diff --git a/public/app/services/tags.service.js b/public/app/services/tags.service.js
new file mode 100644
index 0000000..a912b5f
--- /dev/null
+++ b/public/app/services/tags.service.js
@@ -0,0 +1,21 @@
+export default class Tags {
+ constructor(AppConstants, $http) {
+ 'ngInject';
+
+ this._AppConstants = AppConstants;
+ this._$http = $http;
+
+
+ }
+
+ getAll() {
+
+ return this._$http({
+ url: this._AppConstants.api + '/tags',
+ method: 'GET',
+ }).then((res) => res.data.tags);
+
+ }
+
+
+}
\ No newline at end of file
diff --git a/public/app/services/user.service.js b/public/app/services/user.service.js
new file mode 100644
index 0000000..8e5b68e
--- /dev/null
+++ b/public/app/services/user.service.js
@@ -0,0 +1,92 @@
+export default class User {
+ constructor(JWT, AppConstants, $http, $state, $q) {
+ 'ngInject';
+
+ this._JWT = JWT;
+ this._AppConstants = AppConstants;
+ this._$http = $http;
+ this._$state = $state;
+ this._$q = $q;
+
+ this.current = null;
+ }
+
+ attemptAuth(type, credentials) {
+ let route = (type === 'login') ? '/login' : '';
+ return this._$http({
+ url: this._AppConstants.api + '/users' + route,
+ method: 'POST',
+ data: {
+ user: credentials
+ }
+ }).then(
+ (res) => {
+ this._JWT.save(res.data.user.token);
+ this.current = res.data.user;
+
+ return res;
+ }
+ );
+ }
+
+ logout() {
+ this.current = null;
+ this._JWT.destroy();
+ this._$state.go(this._$state.$current, null, { reload: true });
+ }
+
+ verifyAuth() {
+ let deferred = this._$q.defer();
+
+ if (!this._JWT.get()) {
+ deferred.resolve(false);
+ return deferred.promise;
+ }
+
+ if (this.current) {
+ deferred.resolve(true);
+ } else {
+ this._$http({
+ url: this._AppConstants.api + '/user',
+ method: 'GET',
+ }).then(
+ (res) => {
+ this.current = res.data.user;
+ deferred.resolve(true);
+ },
+ (err) => {
+ this._JWT.destroy();
+ deferred.resolve(false);
+ }
+ );
+ }
+ return deferred.promise;
+ }
+
+ ensureAuthIs(bool) {
+ let deferred = this._$q.defer();
+
+ this.verifyAuth().then((authValid) => {
+ if (authValid !== bool) {
+ this._$state.go('app.home');
+ deferred.resolve(false);
+ } else {
+ deferred.resolve(true);
+ }
+ })
+ return deferred.promise;
+ }
+
+ update(fields) {
+ return this._$http({
+ url: this._AppConstants.api + '/user',
+ method: 'PUT',
+ data: { user: fields }
+ }).then(
+ (res) => {
+ this.current = res.data.user;
+ return res.data.user;
+ }
+ );
+ }
+}
\ No newline at end of file
diff --git a/public/javascripts/main.js b/public/assets/javascripts/main.js
similarity index 96%
rename from public/javascripts/main.js
rename to public/assets/javascripts/main.js
index 7d30fb1..324e375 100644
--- a/public/javascripts/main.js
+++ b/public/assets/javascripts/main.js
@@ -13,8 +13,9 @@ $(window).load(function () {
App.init();
for (var i in colors) {
- $('#colorOptions').append(($('