<commit_before>var test = require('unit.js');
var header = require('./header.js').singleton();
var numComments = 0;
var lastPost = null;
var comment = null;
var comment2 = null;

/**
 * Tests all comment related endpoints
 */
describe('Testing all comment related endpoints', function() {

    this.timeout(20000);

    it('Fetched all comments', function(done){
        header.modepressAgent
            .get('/api/comments').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.bool(res.body.error).isNotTrue()
                    .number(res.body.count)

                numComments = res.body.count;
                done();
            });
    })

    it('Cannot create a comment when not logged in', function(done) {
        header.modepressAgent
            .post('/api/posts/123456789012345678901234/comments/123456789012345678901234').set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .send( {  } )
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("You must be logged in to make this request")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('Cannot create a comment with a badly formatted post id', function(done) {
        header.modepressAgent
            .post('/api/posts/bad/comments/bad').set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .send( {  } )
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("Invalid ID format")
                test.bool(res.body.error).isTrue()

                done();
            });
    })

    it('Cannot create a comment with a badly formatted parent comment id', function(done) {
        header.modepressAgent
            .post('/api/posts/123456789012345678901234/comments/bad').set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .send( {  } )
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("Invalid ID format")
                test.bool(res.body.error).isTrue()

                done();
            });
    })

    it('Cannot create a comment without a post that actually exists', function(done) {
        header.modepressAgent
            .post('/api/posts/123456789012345678901234/comments/123456789012345678901234').set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .send( {  } )
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("post does not exist")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('Can create a temp post', function(done) {
        header.modepressAgent
            .post('/api/posts/create-post').set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .send( {
                title: "Simple Test",
                slug: "--simple--test--",
                content: "Hello world __filter__"
            })
            .end(function(err, res) {
                test.string(res.body.data._id)
                lastPost = res.body.data;
                done();
            });
    })

    it('Cannot create a comment on a post that does exist with illegal html', function(done) {
        header.modepressAgent
            .post(`/api/posts/${lastPost._id}/comments`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .send( { content: "Hello world! __filter__ <script type='text/javascript'>alert(\"BOOO\")</script>" } )
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("'content' has html code that is not allowed")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('Can create a comment on a valid post', function(done) {
        header.modepressAgent
            .post(`/api/posts/${lastPost._id}/comments`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .send( { content: "Hello world! __filter__", public: false } )
            .end(function(err, res) {
                if (err)
                    return done(err);

                comment = res.body.data;
                test.string(res.body.message).is("New comment created")
                test.string(res.body.data._id)
                test.string(res.body.data.author)
                test.value(res.body.data.parent).isNull()
                test.string(res.body.data.post).is(lastPost._id)
                test.string(res.body.data.content).is("Hello world! __filter__")
                test.bool(res.body.data.public).isFalse()
                test.number(res.body.data.createdOn)
                test.number(res.body.data.lastUpdated)
                test.bool(res.body.error).isFalse()
                done();
            });
    })

    it('Cannot get a comment with an invalid id', function(done) {
        header.modepressAgent
            .get(`/api/comments/BADID`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);
                test.string(res.body.message).is("Invalid ID format")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('Cannot get a comment that does not exist', function(done) {
        header.modepressAgent
            .get(`/api/comments/123456789012345678901234`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);
                test.string(res.body.message).is("Could not find comment")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('Can get a valid comment by ID', function(done) {
        header.modepressAgent
            .get(`/api/comments/${comment._id}`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);
                test.string(res.body.message).is("Found 1 comments")
                test.string(res.body.data._id).is(comment._id)
                test.bool(res.body.error).isFalse()
                done();
            });
    })

    it('Cannot get a private comment without being logged in', function(done) {
        header.modepressAgent
            .get(`/api/comments/${comment._id}`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .end(function(err, res) {
                if (err)
                    return done(err);
                test.string(res.body.message).is("That comment is marked private")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('Can create a second public comment on the same post', function(done) {
        header.modepressAgent
            .post(`/api/posts/${lastPost._id}/comments`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .send( { content: "Hello world 2! __filter__", public: true } )
            .end(function(err, res) {
                if (err)
                    return done(err);

                comment2 = res.body.data;
                test.string(res.body.message).is("New comment created")
                done();
            });
    })

    it('Can get a public comment without being logged in', function(done) {
        header.modepressAgent
            .get(`/api/comments/${comment2._id}`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("Found 1 comments")
                test.string(res.body.data._id).is(comment2._id)
                test.bool(res.body.error).isFalse()
                done();
            });
    })

    it('Can get comments by user & there are more than 1', function(done) {
        header.modepressAgent
            .get(`/api/users/${header.uconfig.adminUser.username}/comments`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.number(res.body.count)
                test.bool(res.body.count >= 2).isTrue()
                test.bool(res.body.error).isFalse()
                done();
            });
    })

    it('Can get comments by user & there should be 2 if we filter by keyword', function(done) {
        header.modepressAgent
            .get(`/api/users/${header.uconfig.adminUser.username}/comments?keyword=__filter__`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.number(res.body.count)
                test.bool(res.body.count == 2).isTrue()
                test.bool(res.body.error).isFalse()
                done();
            });
    })

    it('Can get comments by user & should limit whats returned to 1', function(done) {
        header.modepressAgent
            .get(`/api/users/${header.uconfig.adminUser.username}/comments?keyword=__filter__&limit=1`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.number(res.body.count)
                test.bool(res.body.count == 1).isTrue()
                test.bool(res.body.error).isFalse()
                done();
            });
    })

    it('cannot delete a comment with a bad id', function(done){
        header.modepressAgent
            .delete(`/api/users/${header.uconfig.adminUser.username}/comments/abc`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("Invalid ID format")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('cannot delete a comment with a valid id but doesn\'t exist', function(done){
        header.modepressAgent
            .delete(`/api/users/${header.uconfig.adminUser.username}/comments/123456789012345678901234`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("Could not find a comment with that ID")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('cannot delete a comment with an invalid user', function(done){
        header.modepressAgent
            .delete(`/api/users/BADUSER/comments/123456789012345678901234`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("User BADUSER does not exist")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('Can delete an existing comment', function(done) {
        header.modepressAgent
            .delete(`/api/users/${header.uconfig.adminUser.username}/comments/${comment._id}`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("Comment has been successfully removed")
                test.bool(res.body.error).isFalse()
                done();
            });
    })

    it('Can delete the temp post', function(done) {
        header.modepressAgent
            .delete('/api/posts/remove-post/' + lastPost._id).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                test.string(res.body.message).is("Post has been successfully removed")
                done();
            });
    })

    it('Cannot get the second comment as it should have been deleted when the post was', function(done) {
        header.modepressAgent
            .get(`/api/comments/${comment2._id}`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("Could not find comment")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('should have the same number of comments as before the tests started', function(done){
        header.modepressAgent
            .get('/api/comments').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.number(res.body.count)
                test.bool(numComments == res.body.count).isTrue();
                test.bool(res.body.error).isNotTrue()
                done();
            });
    })

});<commit_msg>Bug fix: Fixed logical errors in the comment tests<commit_after>var test = require('unit.js');
var header = require('./header.js').singleton();
var numComments = 0;
var lastPost = null;
var comment = null;
var comment2 = null;

/**
 * Tests all comment related endpoints
 */
describe('Testing all comment related endpoints', function() {

    this.timeout(20000);

    it('Fetched all comments', function(done){
        header.modepressAgent
            .get('/api/comments').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.bool(res.body.error).isNotTrue()
                    .number(res.body.count)

                numComments = res.body.count;
                done();
            });
    })

    it('Cannot create a comment when not logged in', function(done) {
        header.modepressAgent
            .post('/api/posts/123456789012345678901234/comments/123456789012345678901234').set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .send( {  } )
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("You must be logged in to make this request")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('Cannot create a comment with a badly formatted post id', function(done) {
        header.modepressAgent
            .post('/api/posts/bad/comments/bad').set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .send( {  } )
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("Invalid ID format")
                test.bool(res.body.error).isTrue()

                done();
            });
    })

    it('Cannot create a comment with a badly formatted parent comment id', function(done) {
        header.modepressAgent
            .post('/api/posts/123456789012345678901234/comments/bad').set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .send( {  } )
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("Invalid ID format")
                test.bool(res.body.error).isTrue()

                done();
            });
    })

    it('Cannot create a comment without a post that actually exists', function(done) {
        header.modepressAgent
            .post('/api/posts/123456789012345678901234/comments/123456789012345678901234').set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .send( {  } )
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("post does not exist")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('Can create a temp post', function(done) {
        header.modepressAgent
            .post('/api/posts/create-post').set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .send( {
                title: "Simple Test",
                slug: "--simple--test--",
                content: "Hello world __filter__"
            })
            .end(function(err, res) {
                test.string(res.body.data._id)
                lastPost = res.body.data;
                done();
            });
    })

    it('Cannot create a comment on a post that does exist with illegal html', function(done) {
        header.modepressAgent
            .post(`/api/posts/${lastPost._id}/comments`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .send( { content: "Hello world! __filter__ <script type='text/javascript'>alert(\"BOOO\")</script>" } )
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("'content' has html code that is not allowed")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('Can create a comment on a valid post', function(done) {
        header.modepressAgent
            .post(`/api/posts/${lastPost._id}/comments`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .send( { content: "Hello world! __filter__", public: false } )
            .end(function(err, res) {
                if (err)
                    return done(err);

                comment = res.body.data;
                test.string(res.body.message).is("New comment created")
                test.string(res.body.data._id)
                test.string(res.body.data.author)
                test.value(res.body.data.parent).isNull()
                test.string(res.body.data.post).is(lastPost._id)
                test.string(res.body.data.content).is("Hello world! __filter__")
                test.bool(res.body.data.public).isFalse()
                test.number(res.body.data.createdOn)
                test.number(res.body.data.lastUpdated)
                test.bool(res.body.error).isFalse()
                done();
            });
    })

    it('Cannot get a comment with an invalid id', function(done) {
        header.modepressAgent
            .get(`/api/comments/BADID`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);
                test.string(res.body.message).is("Invalid ID format")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('Cannot get a comment that does not exist', function(done) {
        header.modepressAgent
            .get(`/api/comments/123456789012345678901234`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);
                test.string(res.body.message).is("Could not find comment")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('Can get a valid comment by ID', function(done) {
        header.modepressAgent
            .get(`/api/comments/${comment._id}`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);
                test.string(res.body.message).is("Found 1 comments")
                test.string(res.body.data._id).is(comment._id)
                test.bool(res.body.error).isFalse()
                done();
            });
    })

    it('Cannot get a private comment without being logged in', function(done) {
        header.modepressAgent
            .get(`/api/comments/${comment._id}`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .end(function(err, res) {
                if (err)
                    return done(err);
                test.string(res.body.message).is("That comment is marked private")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('Can create a second public comment on the same post', function(done) {
        header.modepressAgent
            .post(`/api/posts/${lastPost._id}/comments`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .send( { content: "Hello world 2! __filter__", public: true } )
            .end(function(err, res) {
                if (err)
                    return done(err);

                comment2 = res.body.data;
                test.string(res.body.message).is("New comment created")
                done();
            });
    })

    it('Can get a public comment without being logged in', function(done) {
        header.modepressAgent
            .get(`/api/comments/${comment2._id}`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("Found 1 comments")
                test.string(res.body.data._id).is(comment2._id)
                test.bool(res.body.error).isFalse()
                done();
            });
    })

    it('Can get comments by user & there are more than 1', function(done) {
        header.modepressAgent
            .get(`/api/users/${header.uconfig.adminUser.username}/comments`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.number(res.body.count)
                test.bool(res.body.count >= 2).isTrue()
                test.bool(res.body.error).isFalse()
                done();
            });
    })

    it('Can get comments by user & there should be 2 if we filter by keyword', function(done) {
        header.modepressAgent
            .get(`/api/users/${header.uconfig.adminUser.username}/comments?keyword=__filter__`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.number(res.body.count)
                test.array(res.body.data).hasLength(2)
                test.bool(res.body.count == 2).isTrue()
                test.bool(res.body.error).isFalse()
                done();
            });
    })

    it('Can get comments by user & should limit whats returned to 1', function(done) {
        header.modepressAgent
            .get(`/api/users/${header.uconfig.adminUser.username}/comments?keyword=__filter__&limit=1`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.number(res.body.count)
                test.array(res.body.data).hasLength(1)
                test.bool(res.body.count == 2).isTrue() // Count is still 2 as
                test.bool(res.body.error).isFalse()
                done();
            });
    })

    it('cannot delete a comment with a bad id', function(done){
        header.modepressAgent
            .delete(`/api/users/${header.uconfig.adminUser.username}/comments/abc`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("Invalid ID format")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('cannot delete a comment with a valid id but doesn\'t exist', function(done){
        header.modepressAgent
            .delete(`/api/users/${header.uconfig.adminUser.username}/comments/123456789012345678901234`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("Could not find a comment with that ID")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('cannot delete a comment with an invalid user', function(done){
        header.modepressAgent
            .delete(`/api/users/BADUSER/comments/123456789012345678901234`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("User BADUSER does not exist")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('Can delete an existing comment', function(done) {
        header.modepressAgent
            .delete(`/api/users/${header.uconfig.adminUser.username}/comments/${comment._id}`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("Comment has been successfully removed")
                test.bool(res.body.error).isFalse()
                done();
            });
    })

    it('Can delete the temp post', function(done) {
        header.modepressAgent
            .delete('/api/posts/remove-post/' + lastPost._id).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                test.string(res.body.message).is("Post has been successfully removed")
                done();
            });
    })

    it('Cannot get the second comment as it should have been deleted when the post was', function(done) {
        header.modepressAgent
            .get(`/api/comments/${comment2._id}`).set('Accept', 'application/json').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.string(res.body.message).is("Could not find comment")
                test.bool(res.body.error).isTrue()
                done();
            });
    })

    it('should have the same number of comments as before the tests started', function(done){
        header.modepressAgent
            .get('/api/comments').expect(200).expect('Content-Type', /json/)
            .set('Cookie', header.adminCookie)
            .end(function(err, res) {
                if (err)
                    return done(err);

                test.number(res.body.count)
                test.bool(numComments == res.body.count).isTrue();
                test.bool(res.body.error).isNotTrue()
                done();
            });
    })

});<|endoftext|>


<commit_before>const merge = require('webpack-merge')
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
const common = require('./webpack.common.js')
const paths = require('./paths')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
const WebpackCleanupPlugin = require('webpack-cleanup-plugin')
const path = require('path')

module.exports = merge(common, {
  bail: true,
  devtool: 'source-map',
  output: {
    publicPath: '/assets/client/',
    filename: 'static/js/[name].[chunkhash:8].js',
    chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js'
  },
  optimization: {
    minimize: true,
    splitChunks: {
      cacheGroups: {
        commons: { test: /[\\/]node_modules[\\/]/, name: "vendors", chunks: "all" }
      }
    }
  },
  plugins: [
    new WebpackCleanupPlugin(),
    new webpack.HashedModuleIdsPlugin(),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')
    }),
    new HtmlWebpackPlugin({
      title: 'ANET',
      publicUrl: '/assets/client/',
      inject: true,
      template: 'public/index.hbs',
      filename: path.resolve(paths.appBuild,'../../views/index.ftl'),
      minify: {
          removeComments: true,
          collapseWhitespace: true,
          removeRedundantAttributes: true,
          useShortDoctype: true,
          removeEmptyAttributes: true,
          removeStyleLinkTypeAttributes: true,
          keepClosingSlash: true,
          minifyJS: true,
          minifyCSS: true,
          minifyURLs: true
        }
  }),
]})
<commit_msg>NCI-Agency/anet#3 Provide uglyfyjs workaround for codemirror<commit_after>const merge = require('webpack-merge')
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
const common = require('./webpack.common.js')
const paths = require('./paths')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
const WebpackCleanupPlugin = require('webpack-cleanup-plugin')
const path = require('path')

module.exports = merge(common, {
  bail: true,
  devtool: 'source-map',
  output: {
    publicPath: '/assets/client/',
    filename: 'static/js/[name].[chunkhash:8].js',
    chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js'
  },
  optimization: {
    minimize: true,
    minimizer: [
      new UglifyJSPlugin({
        uglifyOptions: {
          compress: {
            // hack to make codemirror work with uglifyjs: https://github.com/webpack/webpack/issues/6760
            inline: 1,
          },
          parallel: true,
        }
      })
    ],
    splitChunks: {
      cacheGroups: {
        commons: { test: /[\\/]node_modules[\\/]/, name: "vendors", chunks: "all" }
      }
    }
  },
  plugins: [
    new WebpackCleanupPlugin(),
    new webpack.HashedModuleIdsPlugin(),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')
    }),
    new HtmlWebpackPlugin({
      title: 'ANET',
      publicUrl: '/assets/client/',
      inject: true,
      template: 'public/index.hbs',
      filename: path.resolve(paths.appBuild,'../../views/index.ftl'),
      minify: {
          removeComments: true,
          collapseWhitespace: true,
          removeRedundantAttributes: true,
          useShortDoctype: true,
          removeEmptyAttributes: true,
          removeStyleLinkTypeAttributes: true,
          keepClosingSlash: true,
          minifyJS: true,
          minifyCSS: true,
          minifyURLs: true
        }
  }),
]})
<|endoftext|>


<commit_before>const each = require('async/each')
const omit = require('lodash.omit')
const pick = require('lodash.pick')

const models = require('../models')

function makeFindQuery (req, query, isPublic) {
  const moreParams = { trashed: undefined }
  if (!isPublic) {
    moreParams.user = req.user.email
  }
  return Object.assign(query, moreParams)
}

function makeGenericModelResponseFn (res) {
  return (err, data) => {
    res.set('Content-Type', 'application/json')
    if (err) return serverError(res, err)
    res.json(data)
  }
}

function makeGetModelResponseFn (childModels, res, isCollection, returnChildrenAsEntities) {
  const genericResponder = makeGenericModelResponseFn(res)
  return (err, data) => {
    if (err) return genericResponder(err)
    if (!Array.isArray(data)) {
      data = [data]
    }

    respondWithData({
      childModels,
      data,
      genericResponder,
      isCollection,
      returnChildrenAsEntities
    })
  }
}

function makePublicGetModelResponseFn (config, req, res, isCollection, returnChildrenAsEntities) {
  const genericResponder = makeGenericModelResponseFn(res)
  return (err, data) => {
    if (err) return genericResponder(err)
    if (!Array.isArray(data)) {
      data = [data]
    }

    function doRespond () {
      respondWithData({
        childModels: config.childModels,
        data,
        genericResponder,
        isCollection,
        returnChildrenAsEntities
      })
    }

    function sendDataIfEntityAllowsPublicAccess (entity) {
      if (entity && entity.reportConfig && entity.reportConfig.isPublic) {
        doRespond()
      } else {
        userError(res, 'Public access not allowed')
      }
    }

    function validateRequestingEntity () {
      if (!req.query.requester || !req.query.requester.entity) {
        return userError(res, 'requesting entity not set')
      }

      const {entity, id} = req.query.requester

      // fetch data
      switch (entity) {
        case 'site':
          models.Site.findOne(
            makeFindQuery(null, { _id: id }, true),
            (err, site) => {
              if (err) return genericResponder(err)
              if (!site) return userError(res, 'referenced site not found')
              if (site.id !== req.query.siteId) {
                return userError(res, 'invalid request: entity id mismatch')
              }
              sendDataIfEntityAllowsPublicAccess(site)
            }
          )
          break
        case 'multi-site':
          models.MultiSite.findOne(
            makeFindQuery(null, { _id: id }, true),
            (err, multiSite) => {
              if (err) return genericResponder(err)
              if (!multiSite) return userError(res, 'referenced multisite not found')
              if (!multiSite.sites.every(siteId => req.query._id['$in'].indexOf(siteId) > -1)) {
                return userError(res, 'invalid request: entity ids mismatch')
              }
              sendDataIfEntityAllowsPublicAccess(multiSite)
            }
          )
          break
        default:
          return userError(res, 'invalid requester entity')
      }
    }

    switch (config.name) {
      case 'commuter':
      case 'polygon':
        if (!isCollection) {
          return userError(res, `Public get on single ${config.name} not allowed`)
        }
        validateRequestingEntity()
        break
      case 'multi-site':
        if (isCollection) {
          return userError(res, `Public get on mutliple multi-sites not allowed`)
        }
        sendDataIfEntityAllowsPublicAccess(data[0])
        break
      case 'site':
        if (isCollection) {
          validateRequestingEntity()
        } else {
          sendDataIfEntityAllowsPublicAccess(data[0])
        }
        break
      default:
        return serverError('Undefined config for public request')
    }
  }
}

/**
 * Make public rest endpoints
 *
 * @param  {Object} app The express app
 * @param  {Object} cfg Config object that has everything makeRestEndpoints
 *   has except for the commands option
 */
module.exports.makePublicRestEndpoints = function (app, cfg) {
  const model = cfg.model
  const modelFields = Object.keys(model.schema.paths)
  const name = cfg.name
  const returnChildrenAsEntities = cfg.returnChildrenAsEntities

  app.get(`/public-api/${name}`, (req, res) => {
    // TODO: security concern: findQuery uses any parsed json, allowing any kind of mongoose query
    model.find(
      makeFindQuery(req, pick(req.query, modelFields), true),
      makePublicGetModelResponseFn(cfg, req, res, true, returnChildrenAsEntities)
    )
  })

  app.get(`/public-api/${name}/:id`, (req, res) => {
    model.findOne(
      makeFindQuery(req, { _id: req.params.id }, true),
      makePublicGetModelResponseFn(cfg, req, res, false, returnChildrenAsEntities)
    )
  })
}

/**
 * Make a rest endpoint with the specified routes
 *
 * @param  {Object} app         The express app
 * @param  {Object} jwt         The jwt middleware if it should be used
 * @param  {Object} cfg         Configuration object with the following keys:
 * - {Object} commands    Keys representing commands to make and their corresponding options
 * - {String} name        The endpoint name
 * - {Object} model       The mongo model to use
 * - {Bool} returnChildrenAsEntities  If true, returns children as entities, otherwise returns list of IDs
 * - {Array} childModels  An optional array of object cfgs describing child relationships
 *   Has the following keys
 *   - {String} foreignKey     Foreign key field name in child model
 *   - {String} key            Children field to add in the paret data output
 *   - {Object} model          Child model
 */
module.exports.makeRestEndpoints = function (app, jwt, cfg) {
  const commands = cfg.commands
  const model = cfg.model
  const modelFields = Object.keys(model.schema.paths)
  const name = cfg.name
  const returnChildrenAsEntities = cfg.returnChildrenAsEntities
  if (commands['Collection DELETE']) {
    app.delete(`/api/${name}`, jwt, (req, res) => {
      // TODO: security concern: findQuery uses any parsed json, allowing any kind of mongoose query
      const removeQuery = makeFindQuery(req, pick(req.query, modelFields))
      model.remove(removeQuery, makeGetModelResponseFn(cfg.childModels, res, true, returnChildrenAsEntities))
    })
  }

  if (commands['Collection GET']) {
    app.get(`/api/${name}`, jwt, (req, res) => {
      // TODO: security concern: findQuery uses any parsed json, allowing any kind of mongoose query
      const findQuery = makeFindQuery(req, pick(req.query, modelFields))
      model.find(findQuery, makeGetModelResponseFn(cfg.childModels, res, true, returnChildrenAsEntities))
    })
  }

  if (commands['Collection POST']) {
    app.post(`/api/${name}`, jwt, (req, res) => {
      res.set('Content-Type', 'application/json')
      if (!Array.isArray(req.body)) return userError(res, 'Invalid input data.  Expected an array.')
      const inputData = req.body.map((entity) => Object.assign(entity, { user: req.user.email }))
      model.create(inputData, makeGetModelResponseFn(cfg.childModels, res, true, returnChildrenAsEntities))
    })
  }

  if (commands['DELETE']) {
    app.delete(`/api/${name}/:id`, jwt, (req, res) => {
      // don't use findByIdAndUpdate because it doesn't trigger pre('save') hook
      model.findOne(makeFindQuery(req, { _id: req.params.id }), (err, doc) => {
        if (err) return serverError(res, err)
        const modelResponder = makeGenericModelResponseFn(res)
        if (!doc) return userError(res, 'a database error occurred: record not found')
        doc.trash((err) => {
          modelResponder(err, doc)
        })
      })
    })
  }

  if (commands['GET']) {
    app.get(`/api/${name}/:id`, jwt, (req, res) => {
      model.findOne(makeFindQuery(req, { _id: req.params.id }),
        makeGetModelResponseFn(cfg.childModels, res, false, returnChildrenAsEntities))
    })
  }

  if (commands['PUT']) {
    app.put(`/api/${name}/:id`, jwt, (req, res) => {
      // don't use findByIdAndUpdate because it doesn't trigger pre('save') hook
      model.findOne(makeFindQuery(req, { _id: req.params.id }), (err, doc) => {
        if (err) return serverError(res, err)
        if (!doc) return userError(res, 'a database error occurred: record not found')
        doc.set(omit(req.body, 'user'))
        doc.save(makeGetModelResponseFn(cfg.childModels, res, false, returnChildrenAsEntities))
      })
    })
  }
}

function respondWithData ({
  childModels,
  data,
  genericResponder,
  isCollection,
  returnChildrenAsEntities
}) {
  if (!childModels) return genericResponder(null, isCollection ? data : data[0])

  // wow, awesome, with normalized mongoose models I get to make extra queries
  // to the db to do joins!  </sarcasm>
  const outputData = []
  each(data, (entity, entityCb) => {
    const curEntity = Object.assign({}, entity._doc)
    each(childModels, (childModel, childCb) => {
      childModel.model.find({
        [childModel.foreignKey]: curEntity._id,
        trashed: undefined,
        user: curEntity.user
      }, (err, childEntities) => {
        if (err) return childCb(err)
        if (returnChildrenAsEntities || childModel.returnChildrenAsEntities) {
          curEntity[childModel.key] = childEntities
        } else {
          curEntity[childModel.key] = childEntities.map((childEntity) => childEntity._id)
        }
        childCb()
      })
    }, (err) => {
      if (err) return entityCb(err)
      outputData.push(curEntity)
      entityCb()
    })
  }, (err) => {
    genericResponder(err, isCollection ? outputData : outputData[0])
  })
}

function respondWithError (res, status, error) {
  console.error(error)
  res.status(status).json({ error })
}

function serverError (res, error) {
  respondWithError(res, 500, error)
}

function userError (res, error) {
  respondWithError(res, 400, error)
}
<commit_msg>build(heroku): add debugging log statement<commit_after>const each = require('async/each')
const omit = require('lodash.omit')
const pick = require('lodash.pick')

const models = require('../models')

function makeFindQuery (req, query, isPublic) {
  const moreParams = { trashed: undefined }
  if (!isPublic) {
    moreParams.user = req.user.email
  }
  const dbQuery = Object.assign(query, moreParams)
  console.log(dbQuery)
  return dbQuery
}

function makeGenericModelResponseFn (res) {
  return (err, data) => {
    res.set('Content-Type', 'application/json')
    if (err) return serverError(res, err)
    res.json(data)
  }
}

function makeGetModelResponseFn (childModels, res, isCollection, returnChildrenAsEntities) {
  const genericResponder = makeGenericModelResponseFn(res)
  return (err, data) => {
    if (err) return genericResponder(err)
    if (!Array.isArray(data)) {
      data = [data]
    }

    respondWithData({
      childModels,
      data,
      genericResponder,
      isCollection,
      returnChildrenAsEntities
    })
  }
}

function makePublicGetModelResponseFn (config, req, res, isCollection, returnChildrenAsEntities) {
  const genericResponder = makeGenericModelResponseFn(res)
  return (err, data) => {
    if (err) return genericResponder(err)
    if (!Array.isArray(data)) {
      data = [data]
    }

    function doRespond () {
      respondWithData({
        childModels: config.childModels,
        data,
        genericResponder,
        isCollection,
        returnChildrenAsEntities
      })
    }

    function sendDataIfEntityAllowsPublicAccess (entity) {
      if (entity && entity.reportConfig && entity.reportConfig.isPublic) {
        doRespond()
      } else {
        userError(res, 'Public access not allowed')
      }
    }

    function validateRequestingEntity () {
      if (!req.query.requester || !req.query.requester.entity) {
        return userError(res, 'requesting entity not set')
      }

      const {entity, id} = req.query.requester

      // fetch data
      switch (entity) {
        case 'site':
          models.Site.findOne(
            makeFindQuery(null, { _id: id }, true),
            (err, site) => {
              if (err) return genericResponder(err)
              if (!site) return userError(res, 'referenced site not found')
              if (site.id !== req.query.siteId) {
                return userError(res, 'invalid request: entity id mismatch')
              }
              sendDataIfEntityAllowsPublicAccess(site)
            }
          )
          break
        case 'multi-site':
          models.MultiSite.findOne(
            makeFindQuery(null, { _id: id }, true),
            (err, multiSite) => {
              if (err) return genericResponder(err)
              if (!multiSite) return userError(res, 'referenced multisite not found')
              if (!multiSite.sites.every(siteId => req.query._id['$in'].indexOf(siteId) > -1)) {
                return userError(res, 'invalid request: entity ids mismatch')
              }
              sendDataIfEntityAllowsPublicAccess(multiSite)
            }
          )
          break
        default:
          return userError(res, 'invalid requester entity')
      }
    }

    switch (config.name) {
      case 'commuter':
      case 'polygon':
        if (!isCollection) {
          return userError(res, `Public get on single ${config.name} not allowed`)
        }
        validateRequestingEntity()
        break
      case 'multi-site':
        if (isCollection) {
          return userError(res, `Public get on mutliple multi-sites not allowed`)
        }
        sendDataIfEntityAllowsPublicAccess(data[0])
        break
      case 'site':
        if (isCollection) {
          validateRequestingEntity()
        } else {
          sendDataIfEntityAllowsPublicAccess(data[0])
        }
        break
      default:
        return serverError('Undefined config for public request')
    }
  }
}

/**
 * Make public rest endpoints
 *
 * @param  {Object} app The express app
 * @param  {Object} cfg Config object that has everything makeRestEndpoints
 *   has except for the commands option
 */
module.exports.makePublicRestEndpoints = function (app, cfg) {
  const model = cfg.model
  const modelFields = Object.keys(model.schema.paths)
  const name = cfg.name
  const returnChildrenAsEntities = cfg.returnChildrenAsEntities

  app.get(`/public-api/${name}`, (req, res) => {
    // TODO: security concern: findQuery uses any parsed json, allowing any kind of mongoose query
    model.find(
      makeFindQuery(req, pick(req.query, modelFields), true),
      makePublicGetModelResponseFn(cfg, req, res, true, returnChildrenAsEntities)
    )
  })

  app.get(`/public-api/${name}/:id`, (req, res) => {
    model.findOne(
      makeFindQuery(req, { _id: req.params.id }, true),
      makePublicGetModelResponseFn(cfg, req, res, false, returnChildrenAsEntities)
    )
  })
}

/**
 * Make a rest endpoint with the specified routes
 *
 * @param  {Object} app         The express app
 * @param  {Object} jwt         The jwt middleware if it should be used
 * @param  {Object} cfg         Configuration object with the following keys:
 * - {Object} commands    Keys representing commands to make and their corresponding options
 * - {String} name        The endpoint name
 * - {Object} model       The mongo model to use
 * - {Bool} returnChildrenAsEntities  If true, returns children as entities, otherwise returns list of IDs
 * - {Array} childModels  An optional array of object cfgs describing child relationships
 *   Has the following keys
 *   - {String} foreignKey     Foreign key field name in child model
 *   - {String} key            Children field to add in the paret data output
 *   - {Object} model          Child model
 */
module.exports.makeRestEndpoints = function (app, jwt, cfg) {
  const commands = cfg.commands
  const model = cfg.model
  const modelFields = Object.keys(model.schema.paths)
  const name = cfg.name
  const returnChildrenAsEntities = cfg.returnChildrenAsEntities
  if (commands['Collection DELETE']) {
    app.delete(`/api/${name}`, jwt, (req, res) => {
      // TODO: security concern: findQuery uses any parsed json, allowing any kind of mongoose query
      const removeQuery = makeFindQuery(req, pick(req.query, modelFields))
      model.remove(removeQuery, makeGetModelResponseFn(cfg.childModels, res, true, returnChildrenAsEntities))
    })
  }

  if (commands['Collection GET']) {
    app.get(`/api/${name}`, jwt, (req, res) => {
      // TODO: security concern: findQuery uses any parsed json, allowing any kind of mongoose query
      const findQuery = makeFindQuery(req, pick(req.query, modelFields))
      model.find(findQuery, makeGetModelResponseFn(cfg.childModels, res, true, returnChildrenAsEntities))
    })
  }

  if (commands['Collection POST']) {
    app.post(`/api/${name}`, jwt, (req, res) => {
      res.set('Content-Type', 'application/json')
      if (!Array.isArray(req.body)) return userError(res, 'Invalid input data.  Expected an array.')
      const inputData = req.body.map((entity) => Object.assign(entity, { user: req.user.email }))
      model.create(inputData, makeGetModelResponseFn(cfg.childModels, res, true, returnChildrenAsEntities))
    })
  }

  if (commands['DELETE']) {
    app.delete(`/api/${name}/:id`, jwt, (req, res) => {
      // don't use findByIdAndUpdate because it doesn't trigger pre('save') hook
      model.findOne(makeFindQuery(req, { _id: req.params.id }), (err, doc) => {
        if (err) return serverError(res, err)
        const modelResponder = makeGenericModelResponseFn(res)
        if (!doc) return userError(res, 'a database error occurred: record not found')
        doc.trash((err) => {
          modelResponder(err, doc)
        })
      })
    })
  }

  if (commands['GET']) {
    app.get(`/api/${name}/:id`, jwt, (req, res) => {
      model.findOne(makeFindQuery(req, { _id: req.params.id }),
        makeGetModelResponseFn(cfg.childModels, res, false, returnChildrenAsEntities))
    })
  }

  if (commands['PUT']) {
    app.put(`/api/${name}/:id`, jwt, (req, res) => {
      // don't use findByIdAndUpdate because it doesn't trigger pre('save') hook
      model.findOne(makeFindQuery(req, { _id: req.params.id }), (err, doc) => {
        if (err) return serverError(res, err)
        if (!doc) return userError(res, 'a database error occurred: record not found')
        doc.set(omit(req.body, 'user'))
        doc.save(makeGetModelResponseFn(cfg.childModels, res, false, returnChildrenAsEntities))
      })
    })
  }
}

function respondWithData ({
  childModels,
  data,
  genericResponder,
  isCollection,
  returnChildrenAsEntities
}) {
  if (!childModels) return genericResponder(null, isCollection ? data : data[0])

  // wow, awesome, with normalized mongoose models I get to make extra queries
  // to the db to do joins!  </sarcasm>
  const outputData = []
  each(data, (entity, entityCb) => {
    const curEntity = Object.assign({}, entity._doc)
    each(childModels, (childModel, childCb) => {
      childModel.model.find({
        [childModel.foreignKey]: curEntity._id,
        trashed: undefined,
        user: curEntity.user
      }, (err, childEntities) => {
        if (err) return childCb(err)
        if (returnChildrenAsEntities || childModel.returnChildrenAsEntities) {
          curEntity[childModel.key] = childEntities
        } else {
          curEntity[childModel.key] = childEntities.map((childEntity) => childEntity._id)
        }
        childCb()
      })
    }, (err) => {
      if (err) return entityCb(err)
      outputData.push(curEntity)
      entityCb()
    })
  }, (err) => {
    genericResponder(err, isCollection ? outputData : outputData[0])
  })
}

function respondWithError (res, status, error) {
  console.error(error)
  res.status(status).json({ error })
}

function serverError (res, error) {
  respondWithError(res, 500, error)
}

function userError (res, error) {
  respondWithError(res, 400, error)
}
<|endoftext|>


<commit_before>(function (window, Plugins, Utils, BinaryHeap, undefined) {
  'use strict';

  // Plugin constructor
  // ------------------

  function Hourline (element, options, planner) {
    var visibility = true;
    var hourline = Utils.createElement(Planner.Templates.hourline());

    element.querySelector('.planner-container').appendChild(hourline);

    scrollScreen();
    moveLine();

    window.setInterval(moveLine, options.hourlineTickUpdate * 3600);

    // Public methods
    // --------------

    this.findHeight = findHeight;
    this.show = show;
    this.hide = hide;
    this.toggle = toggle;
    this.redrawLoop = new EventHeap();

    // Helpers
    // -------

    function findHeight () {
      var date = new Date(),
          effectiveHours = date.getHours() + (date.getMinutes() / 60);

      return (effectiveHours * options.timeslotHeight * options.timeslots) + options.hourlineMargin;
    }

    function scrollScreen () {
      var hourlineTop = findHeight(),
          offset = window.screen.height / options.hourlineScreenSection;

      window.scroll(0, hourlineTop - offset);
    }

    function moveLine () {
      var hourlineTop = findHeight();

      hourline.style.top = hourlineTop + 'px';
    }

    function show () {
      visibility = true;
      hourline.style.display = 'inherit';
    }

    function hide () {
      visibility = false;
      hourline.style.display = 'none';
    }

    function toggle () {
      visibility = !visibility;
      if (visibility) {
        show();
      } else {
        hide();
      }
    }

    // Heap utilities
    // --------------

    function toNextMultiple (of, after) {
      return of - after % of;
    }

    function isPast(card) {
      return card.start <= Date.now();
    }

    // Heap Event looper
    // -----------------

    function EventHeap() {
      function scoreFunction(event) {
        return event.start;
      }

      function toNextRedraw() {
        return toNextMultiple(options.redrawInterval, Date.now()) + 1000;
      }

      function decorate(heap) {
        var _push = heap.push ;

        heap.push = function (card) {
          if (!isPast(card)) {
            // adds the card to the redraw loop
            _push.apply(heap, [card]);

            if (heap.size() === 1) {
              heap.loop();
            }
          } else {
            // this card is already in the past; redraw it
            heap.redraw(card);
          }
        };

        heap.update = function (card) {
          heap.redraw(card);
          heap.remove(card);
          heap.push(card);
        };

        heap.peek = function() {
          return heap.size() ? heap.content[0] : null;
        };

        heap.redraw = function(card) {
          var redrawFn = isPast(card) ? Utils.addClass : Utils.removeClass;
          var doms = planner.mapCard.get(card);

          for (var key in doms) {
            if (doms.hasOwnProperty(key)) {
              redrawFn(doms[key], 'past-event');
            }
          }
        };

        heap.action = function() {
          var event;
          while ((event = heap.peek()) && event.start <= Date.now()) {
            heap.redraw(heap.pop());
          }
        };

        heap.clear = function() {
          heap.content = [];
          if (heap.timer) {
            clearTimeout(heap.timer);
            heap.timer = null;
          }
        };

        heap.loop = function() {
          heap.timer = setTimeout(
            function() {
              heap.action();
              heap.loop();
            },
            toNextRedraw()
          );
        };
      }

      // creating a redraw loop that uses a Heap
      var heap = new BinaryHeap(scoreFunction);
      decorate(heap);

      // attaching to Planner.js events;
      // this makes the Hourline plugin self-sufficient
      planner.events.subscribe('cardCreated', heap.push);
      planner.events.subscribe('cardDeleted', heap.remove);
      planner.events.subscribe('cardUpdated', heap.update);
      planner.events.subscribe('plannerClear', heap.clear);

      return heap;
    }
  }

  // Defaults
  // --------

  Hourline.DEFAULTS = {
    hourlineMargin: 85,
    hourlineScreenSection: 3,
    hourlineTickUpdate: 5,
    redrawInterval: 900000
  };

  // Register this plugin to plugins list
  // ------------------------------------
  Plugins.Hourline = Hourline;
  Plugins.register('hourline', Hourline);

})(window, Planner.Plugins, Planner.Utils, Ds.BinaryHeap);
<commit_msg>attaching heap push to cardDrawn event<commit_after>(function (window, Plugins, Utils, BinaryHeap, undefined) {
  'use strict';

  // Plugin constructor
  // ------------------

  function Hourline (element, options, planner) {
    var visibility = true;
    var hourline = Utils.createElement(Planner.Templates.hourline());

    element.querySelector('.planner-container').appendChild(hourline);

    scrollScreen();
    moveLine();

    window.setInterval(moveLine, options.hourlineTickUpdate * 3600);

    // Public methods
    // --------------

    this.findHeight = findHeight;
    this.show = show;
    this.hide = hide;
    this.toggle = toggle;
    this.redrawLoop = new EventHeap();

    // Helpers
    // -------

    function findHeight () {
      var date = new Date(),
          effectiveHours = date.getHours() + (date.getMinutes() / 60);

      return (effectiveHours * options.timeslotHeight * options.timeslots) + options.hourlineMargin;
    }

    function scrollScreen () {
      var hourlineTop = findHeight(),
          offset = window.screen.height / options.hourlineScreenSection;

      window.scroll(0, hourlineTop - offset);
    }

    function moveLine () {
      var hourlineTop = findHeight();

      hourline.style.top = hourlineTop + 'px';
    }

    function show () {
      visibility = true;
      hourline.style.display = 'inherit';
    }

    function hide () {
      visibility = false;
      hourline.style.display = 'none';
    }

    function toggle () {
      visibility = !visibility;
      if (visibility) {
        show();
      } else {
        hide();
      }
    }

    // Heap utilities
    // --------------

    function toNextMultiple (of, after) {
      return of - after % of;
    }

    function isPast(card) {
      return card.start <= Date.now();
    }

    // Heap Event looper
    // -----------------

    function EventHeap() {
      function scoreFunction(event) {
        return event.start;
      }

      function toNextRedraw() {
        return toNextMultiple(options.redrawInterval, Date.now()) + 1000;
      }

      function decorate(heap) {
        var _push = heap.push ;

        heap.push = function (card) {
          if (!isPast(card)) {
            // adds the card to the redraw loop
            _push.apply(heap, [card]);

            if (heap.size() === 1) {
              heap.loop();
            }
          } else {
            // this card is already in the past; redraw it
            heap.redraw(card);
          }
        };

        heap.update = function (card) {
          heap.redraw(card);
          heap.remove(card);
          heap.push(card);
        };

        heap.peek = function() {
          return heap.size() ? heap.content[0] : null;
        };

        heap.redraw = function(card) {
          var redrawFn = isPast(card) ? Utils.addClass : Utils.removeClass;
          var doms = planner.mapCard.get(card);

          for (var key in doms) {
            if (doms.hasOwnProperty(key)) {
              redrawFn(doms[key], 'past-event');
            }
          }
        };

        heap.action = function() {
          var event;
          while ((event = heap.peek()) && event.start <= Date.now()) {
            heap.redraw(heap.pop());
          }
        };

        heap.clear = function() {
          heap.content = [];
          if (heap.timer) {
            clearTimeout(heap.timer);
            heap.timer = null;
          }
        };

        heap.loop = function() {
          heap.timer = setTimeout(
            function() {
              heap.action();
              heap.loop();
            },
            toNextRedraw()
          );
        };
      }

      // creating a redraw loop that uses a Heap
      var heap = new BinaryHeap(scoreFunction);
      decorate(heap);

      // attaching to Planner.js events;
      // this makes the Hourline plugin self-sufficient
      planner.events.subscribe('cardDrawn', heap.push);
      planner.events.subscribe('cardDeleted', heap.remove);
      planner.events.subscribe('cardUpdated', heap.update);
      planner.events.subscribe('plannerClear', heap.clear);

      return heap;
    }
  }

  // Defaults
  // --------

  Hourline.DEFAULTS = {
    hourlineMargin: 85,
    hourlineScreenSection: 3,
    hourlineTickUpdate: 5,
    redrawInterval: 900000
  };

  // Register this plugin to plugins list
  // ------------------------------------
  Plugins.Hourline = Hourline;
  Plugins.register('hourline', Hourline);

})(window, Planner.Plugins, Planner.Utils, Ds.BinaryHeap);
<|endoftext|>


<commit_before>const isPlainObj = function(o) {
  return typeof o === "object" && o.constructor === Object;
};

const composeReducers = function(reducers) {

  if(!isPlainObj(reducers)) {
    throw new TypeError("composeReducers() only accepts an object of reducer functions");
  }

  return function(state, action) {

    if(!isPlainObj(state)) {
      throw new TypeError("composed reducer expected state to be an object and not: " + typeof state);
    }

    var keys = Object.keys(state);
    var nextState = {}, reducer;

    for(var key of keys) {
      reducer = reducers[key];
      nextState[key] = reducer(state[key], action);
    }

    return nextState;
  };
};

module.exports = composeReducers;
<commit_msg>add better input check for composeReducers<commit_after>const isPlainObj = function(o) {
  return typeof o === "object" && o.constructor === Object;
};

const composeReducers = function(reducers) {

  if(!isPlainObj(reducers)) {
    throw new TypeError("composeReducers() only accepts an object of reducer functions");
  }

  Object.keys(reducers).forEach((key) => {
    if(typeof reducers[key] !== "function") {
      throw new TypeError("composeReducers(): reducers." + key + "is not a function");
    }
  });

  return function(state, action) {

    if(!isPlainObj(state)) {
      throw new TypeError("composed reducer expected state to be an object and not: " + typeof state);
    }

    var keys = Object.keys(state);
    var nextState = {}, reducer;

    for(var key of keys) {
      reducer = reducers[key];
      nextState[key] = reducer(state[key], action);
    }

    return nextState;
  };
};

module.exports = composeReducers;
<|endoftext|>


<commit_before>import Video from 'twilio-video';

function attachTracksToDomElement(tracks, domElement) {
  tracks.forEach(function(track) {
    domElement.appendChild(track.attach());
  });
}

function attachParticipantTracks(participant, domElement) {
  var tracks = Array.from(participant.tracks.values());
  attachTracksToDomElement(tracks, domElement);
}

function muteTracks(tracks) {
  tracks.forEach(function(track) {
    if (track.kind == "audio") {
      track.disable();
    }
  });
}

function unmuteTracks(tracks) {
  tracks.forEach(function(track) {
    if (track.kind == "audio") {
      track.enable();
    }
  });
}

function roomJoined(room, template){
  if (! template.localTracks) {
    const localMediaElement = document.getElementById("local-media");
    attachParticipantTracks(room.localParticipant, localMediaElement);
    template.localTracks = room.localParticipant.tracks;
    template.localMediaAttached.set(true);
  }
}

function createLocalTracks(template) {
  return new Promise((resolve, reject) => {
    const localMediaElement = document.getElementById("local-media");
    Video.createLocalTracks().then(
      function(tracks) {
        template.localTracks = tracks;
        attachTracksToDomElement( tracks, localMediaElement );
        resolve(tracks);
    }, function(error) {
      console.error('Unable to access local media', error);
      reject('Unable to access Camera and Microphone');
    });
  });
}

Template.videoChat.onCreated(function (){
   this.localMediaAttached = new ReactiveVar(null);
   this.localMuted = new ReactiveVar(null);
   const self = this;
   Meteor.call(
     "requestVideoChatAccess", function(error, result){
       if (error) {
         console.error(error.reason)
       }
       if (result) {
         self.accessData = result;
       }
     });

});

Template.videoChat.onRendered(function (){
   this.localMediaAttached.set(false);
   this.localMuted.set(false);
});

Template.videoChat.helpers({
  inPreviewMode: function () {
    return (Template.instance().localMediaAttached.get())
  },
  localMediaMuted: function () {
    return (Template.instance().localMuted.get())
  }
});

Template.videoChat.events({
  "click #js-preview-camera": function(event, template){
    event.preventDefault();
    if( ! template.localTracks){
      createLocalTracks(template).then(
        function(value){
          template.localMediaAttached.set(true);
        },
        function(error){
          console.error(error);
        }
      )
    }
  },
  "click #js-mute-local-media": function(event, template){
    event.preventDefault();
    var localMediaElement = document.getElementById("local-media");
    if( template.localTracks){
      muteTracks(template.localTracks);
      template.localMuted.set(true);
    }
  },
  "click #js-unmute-local-media": function(event, template){
    event.preventDefault();
    var localMediaElement = document.getElementById("local-media");
    if( template.localTracks){
      unmuteTracks(template.localTracks);
      template.localMuted.set(false);
    }
  },
  "submit #js-join-video-chat-room": function(event, template){
    event.preventDefault();
    let theLocalTracks;
    template.localMediaAttached.set(true);
    const roomName = event.target.text.value;
    let connectOptions = {
      name: roomName,
      logLevel: 'warn'
    }
    if ( template.localTracks ) {
      connectOptions.tracks = template.localTracks;
    }
    Video.connect(template.accessData.token, connectOptions)
    .then(
      function(room){
        roomJoined(room,template)
      },
      function(error) {
        console.error('Could not connect to Twilio: ' + error.message);
      }
    );
  }
});
<commit_msg>codestyle<commit_after>import Video from 'twilio-video';

function attachTracksToDomElement(tracks, domElement) {
  tracks.forEach(function(track) {
    domElement.appendChild(track.attach());
  });
}

function attachParticipantTracks(participant, domElement) {
  var tracks = Array.from(participant.tracks.values());
  attachTracksToDomElement(tracks, domElement);
}

function muteTracks(tracks) {
  tracks.forEach(function(track) {
    if (track.kind == "audio") {
      track.disable();
    }
  });
}

function unmuteTracks(tracks) {
  tracks.forEach(function(track) {
    if (track.kind == "audio") {
      track.enable();
    }
  });
}

function roomJoined(room, template){
  if (! template.localTracks) {
    const localMediaElement = document.getElementById("local-media");
    attachParticipantTracks(room.localParticipant, localMediaElement);
    template.localTracks = room.localParticipant.tracks;
    template.localMediaAttached.set(true);
  }
}

function createLocalTracks(template) {
  return new Promise((resolve, reject) => {
    const localMediaElement = document.getElementById("local-media");
    Video.createLocalTracks().then(
      function(tracks) {
        template.localTracks = tracks;
        attachTracksToDomElement( tracks, localMediaElement );
        resolve(tracks);
      },
      function(error) {
        console.error('Unable to access local media', error);
        reject('Unable to access Camera and Microphone');
      }
    );
  });
}

Template.videoChat.onCreated(function (){
   this.localMediaAttached = new ReactiveVar(null);
   this.localMuted = new ReactiveVar(null);
   const self = this;
   Meteor.call(
     "requestVideoChatAccess", function(error, result){
       if (error) {
         console.error(error.reason)
       }
       if (result) {
         self.accessData = result;
       }
     });

});

Template.videoChat.onRendered(function (){
   this.localMediaAttached.set(false);
   this.localMuted.set(false);
});

Template.videoChat.helpers({
  inPreviewMode: function () {
    return (Template.instance().localMediaAttached.get())
  },
  localMediaMuted: function () {
    return (Template.instance().localMuted.get())
  }
});

Template.videoChat.events({
  "click #js-preview-camera": function(event, template){
    event.preventDefault();
    if( ! template.localTracks){
      createLocalTracks(template).then(
        function(value){
          template.localMediaAttached.set(true);
        },
        function(error){
          console.error(error);
        }
      )
    }
  },
  "click #js-mute-local-media": function(event, template){
    event.preventDefault();
    var localMediaElement = document.getElementById("local-media");
    if( template.localTracks){
      muteTracks(template.localTracks);
      template.localMuted.set(true);
    }
  },
  "click #js-unmute-local-media": function(event, template){
    event.preventDefault();
    var localMediaElement = document.getElementById("local-media");
    if( template.localTracks){
      unmuteTracks(template.localTracks);
      template.localMuted.set(false);
    }
  },
  "submit #js-join-video-chat-room": function(event, template){
    event.preventDefault();
    let theLocalTracks;
    template.localMediaAttached.set(true);
    const roomName = event.target.text.value;
    let connectOptions = {
      name: roomName,
      logLevel: 'warn'
    }
    if ( template.localTracks ) {
      connectOptions.tracks = template.localTracks;
    }
    Video.connect(template.accessData.token, connectOptions)
    .then(
      function(room){
        roomJoined(room,template)
      },
      function(error) {
        console.error('Could not connect to Twilio: ' + error.message);
      }
    );
  }
});
<|endoftext|>


<commit_before>import Video from 'twilio-video';

Template.videoChat.onDestroyed(function () {
  const temp = this;
  if (temp.room) {
    temp.room.disconnect();
  }
});

Template.videoChat.helpers({
  twilioVideoNotSupportedByBrowser: function(){
    return (!navigator.webkitGetUserMedia && !navigator.mozGetUserMedia)
  }
});

Template.videoChat.events({
  'submit #js-request-video-chat': function (event, template) {
    event.preventDefault();
    const target = event.target;
    const roomName = target.text.value;
    const userIdendifer = Random.id();
    let initAccessToken;
    let accessToken;
    let room;

    initAccessToken = Meteor.call(
      "requestVideoChatAccess", roomName, userIdendifer,
      function requestVideoChatAccessCallback(error, result) {
        if (error) {
          console.log(error, error.reson);
        }
        if (result) {
          accessToken = result;
          template.client = new Video.Client(accessToken.token);
          room = template.client.connect({
            to: roomName
          })
          .then(
            room => {
              room.localParticipant.media.attach('#local-media');

              room.participants.forEach(function(participant) {
                participant.media.attach('#remote-media');
              });

              room.on('participantConnected', function (participant) {
                participant.media.attach('#remote-media');
              });

              room.on('disconnected', function () {
                room.localParticipant.media.detach();
                room.participants.forEach(function(participant) {
                  participant.media.detach();
                });
              });

              template.room = room;
            }
          )
        }
    })
  },
  "click #js-end-video-chat" : function (event, Template){
    event.preventDefault();
    if (Template.room) {
      Template.room.disconnect();
    }
  }
});
<commit_msg>fixed #6<commit_after>import Video from 'twilio-video';

Template.videoChat.onDestroyed(function (event, template) {
  event.preventDefault();
  if (template.room) {
    template.room.disconnect();
  }
});

Template.videoChat.helpers({
  twilioVideoNotSupportedByBrowser: function(){
    return (!navigator.webkitGetUserMedia && !navigator.mozGetUserMedia)
  }
});

Template.videoChat.events({
  'submit #js-request-video-chat': function (event, template) {
    event.preventDefault();
    const target = event.target;
    const roomName = target.text.value;
    const userIdendifer = Random.id();
    let initAccessToken;
    let accessToken;
    let room;

    initAccessToken = Meteor.call(
      "requestVideoChatAccess", roomName, userIdendifer,
      function requestVideoChatAccessCallback(error, result) {
        if (error) {
          console.log(error, error.reson);
        }
        if (result) {
          accessToken = result;
          template.client = new Video.Client(accessToken.token);
          room = template.client.connect({
            to: roomName
          })
          .then(
            room => {
              room.localParticipant.media.attach('#local-media');

              room.participants.forEach(function(participant) {
                participant.media.attach('#remote-media');
              });

              room.on('participantConnected', function (participant) {
                participant.media.attach('#remote-media');
              });

              room.on('disconnected', function () {
                room.localParticipant.media.detach();
                room.participants.forEach(function(participant) {
                  participant.media.detach();
                });
              });

              template.room = room;
            }
          )
        }
    })
  },
  "click #js-end-video-chat" : function (event, Template){
    event.preventDefault();
    if (Template.room) {
      Template.room.disconnect();
    }
  }
});
<|endoftext|>


<commit_before>Template._topic.helpers({
  alreadyVoted: function() {
    return Meteor.user() && _(Meteor.user().profile.votedTopicIds).contains(this._id)
  },

  author: function() {
    return Meteor.users.findOne({_id: this.userId});
  }
});

Template._topic.events({
  'click [data-vote]': function(event, template) {
    event.preventDefault();

    if (!Meteor.user()) {
      alert("Log in with Meetup to vote!");
      return false;
    }

    var alreadyVoted = _(Meteor.user().profile.votedTopicIds).contains(this._id);
    if (!alreadyVoted) {
      var points = this.points + 1;
      console.log('Topics.update({_id: ' + this._id + '}, {$set: {points: ' + points + '}})');
      Topics.update({_id: this._id}, {$set: {points: points}});
      Meteor.users.update({_id: Meteor.userId()}, {$push: {'profile.votedTopicIds': this._id}});
    }
  }
});
<commit_msg>remove debuggin<commit_after>Template._topic.helpers({
  alreadyVoted: function() {
    return Meteor.user() && _(Meteor.user().profile.votedTopicIds).contains(this._id)
  },

  author: function() {
    return Meteor.users.findOne({_id: this.userId});
  }
});

Template._topic.events({
  'click [data-vote]': function(event, template) {
    event.preventDefault();

    if (!Meteor.user()) {
      alert("Log in with Meetup to vote!");
      return false;
    }

    var alreadyVoted = _(Meteor.user().profile.votedTopicIds).contains(this._id);
    if (!alreadyVoted) {
      var points = this.points + 1;
      Topics.update({_id: this._id}, {$set: {points: points}});
      Meteor.users.update({_id: Meteor.userId()}, {$push: {'profile.votedTopicIds': this._id}});
    }
  }
});
<|endoftext|>


<commit_before>Template[getTemplate('invites')].helpers({
  canCurrentUserInvite: function(){
    var currentUser = Meteor.user();
    return currentUser && (currentUser.inviteCount > 0 && canInvite(currentUser));
  },

  invitesLeft: function(){
    var currentUser = Meteor.user();
    return currentUser ? currentUser.inviteCount : 0;
  },

  invitesSchema: function() {
    // expose schema for Invites (used by AutoForm)
    return InviteSchema;
  }
});

var scrollUp = function(){
  Deps.afterFlush(function() {
    var element = $('.grid > .error');
    $('html, body').animate({scrollTop: element.offset().top});
  });
};

AutoForm.hooks({
  inviteForm: {
    onSuccess: function(operation, result, template) {
      clearSeenErrors();

      if(result && result.newUser){
        throwError('Almost there. Please give your friend a sign up link to get in.');
      } else {
        throwError('Thank you!');
      }
      scrollUp();
    },

    onError: function(operation, error, template) {
      clearSeenErrors();

      if(error && error.reason){
        throwError(error.reason);
        scrollUp();
      }
    }
  }
});<commit_msg>update message upon successful invite<commit_after>Template[getTemplate('invites')].helpers({
  canCurrentUserInvite: function(){
    var currentUser = Meteor.user();
    return currentUser && (currentUser.inviteCount > 0 && canInvite(currentUser));
  },

  invitesLeft: function(){
    var currentUser = Meteor.user();
    return currentUser ? currentUser.inviteCount : 0;
  },

  invitesSchema: function() {
    // expose schema for Invites (used by AutoForm)
    return InviteSchema;
  }
});

var scrollUp = function(){
  Deps.afterFlush(function() {
    var element = $('.grid > .error');
    $('html, body').animate({scrollTop: element.offset().top});
  });
};

AutoForm.hooks({
  inviteForm: {
    onSuccess: function(operation, result, template) {
      clearSeenErrors();

      if(result && result.newUser){
        throwError('An invite has been sent out. Thank you!');
      } else {
        throwError('Thank you!');
      }
      scrollUp();
    },

    onError: function(operation, error, template) {
      clearSeenErrors();

      if(error && error.reason){
        throwError(error.reason);
        scrollUp();
      }
    }
  }
});<|endoftext|>


<commit_before>/**
 * typestring - In-memory TypeScript compiler
 * https://github.com/gavinhungry/typestring
 */

// http://www.2ality.com/2011/11/module-gap.html
({
  define: typeof define === 'function' ? define :
  function(reqs, fn) { module.exports = fn.apply(null, reqs.map(require)); }
}).

define(['typescript-api'], function (TypeScript) {
  'use strict';

  var filename = '_typestring.ts';

  return {

    /**
     * Compile a string of TypeScript, return as a string of JavaScript
     *
     * @param {String} input - TypeScript to compile
     * @param {Object} [refs] - map of referenced filenames to content
     * @return {String} JavaScript output
     * @throws TypeScript compile error
     */
    compile: function(input, refs) {
      var compiler = new TypeScript.TypeScriptCompiler();
      refs = refs || {};

      // replace references with script strings
      var re = new RegExp(TypeScript.tripleSlashReferenceRegExp.source, 'gm');
      input = input.replace(re, function(match, p1, p2, filename) {
        return refs[filename] || match;
      });

      var snapshot = TypeScript.ScriptSnapshot.fromString(input);
      compiler.addFile(filename, snapshot);

      var iter = compiler.compile();

      var output = '';
      while(iter.moveNext()) {
        var current = iter.current().outputFiles[0];
        output += !!current ? current.text : '';
      }

      var diagnostics = compiler.getSemanticDiagnostics(filename);
      if (!output && diagnostics.length) {
        throw new Error(diagnostics[0].text());
      }

      return output;
    }
  };
});
<commit_msg>throw syntax errors instead of semantic ones. Also, throw all encountered errors instead of just the first.<commit_after>/**
 * typestring - In-memory TypeScript compiler
 * https://github.com/gavinhungry/typestring
 */

// http://www.2ality.com/2011/11/module-gap.html
({
  define: typeof define === 'function' ? define :
  function(reqs, fn) { module.exports = fn.apply(null, reqs.map(require)); }
}).

define(['typescript-api'], function (TypeScript) {
  'use strict';

  var filename = '_typestring.ts';

  return {

    /**
     * Compile a string of TypeScript, return as a string of JavaScript
     *
     * @param {String} input - TypeScript to compile
     * @param {Object} [refs] - map of referenced filenames to content
     * @return {String} JavaScript output
     * @throws TypeScript compile error
     */
    compile: function(input, refs) {
      var compiler = new TypeScript.TypeScriptCompiler();
      refs = refs || {};

      // replace references with script strings
      var re = new RegExp(TypeScript.tripleSlashReferenceRegExp.source, 'gm');
      input = input.replace(re, function(match, p1, p2, filename) {
        return refs[filename] || match;
      });

      var snapshot = TypeScript.ScriptSnapshot.fromString(input);
      compiler.addFile(filename, snapshot);

      var iter = compiler.compile();

      var output = '';
      while(iter.moveNext()) {
        var current = iter.current().outputFiles[0];
        output += !!current ? current.text : '';
      }

      var diagnostics = compiler.getSyntacticDiagnostics(filename);
      if (diagnostics.length) {
        throw diagnostics;
      }

      return output;
    }
  };
});
<|endoftext|>


<commit_before>/**
 * All collections should descend from this collection base class, which
 * provides nice utilities for pagination and sorting.
 */
girder.Collection = Backbone.Collection.extend({
    resourceName: null,

    sortField: 'name',
    sortDir: girder.SORT_ASC,

    // Number of records to fetch per page
    pageLimit: 25,
    offset: 0,

    // Alternative fetch URL
    altUrl: null,

    /**
     * Append mode can be used to append pages to the collection rather than
     * simply replacing its contents when a new page is fetched. For the sake
     * of least surprise, this property should not be changed in the definition
     * of collections, but after they are instantiated.
     */
    append: false,

    /**
     * Returns a boolean of whether or not this collection has previous pages,
     * i.e. if the offset of the current page start is > 0
     */
    hasPreviousPage: function () {
        return this.offset - this.length > 0;
    },

    /**
     * After you have called fetch() on a collection, this method will tell
     * you whether there are more pages remaining to be fetched, or if you
     * have hit the end.
     */
    hasNextPage: function () {
        return this._hasMorePages;
    },

    /**
     * Fetch the previous page of this collection, emitting g:changed when done.
     */
    fetchPreviousPage: function (params) {
        this.offset = Math.max(0, this.offset - this.length - this.pageLimit);
        this.fetch(params);
    },

    /**
     * Fetch the previous page of this collection, emitting g:changed when done.
     */
    fetchNextPage: function (params) {
        this.fetch(_.extend(this.params, params || {}));
    },

    /**
     * Return the 0-indexed page number of the current page. Add 1 to this
     * result when displaying it to the user.
     */
    pageNum: function (params) {
        return Math.ceil((this.offset - this.length) / this.pageLimit);
    },

    /**
     * Fetches the next page of this collection, replacing the existing models
     * of this collection with the requested page. If the next page contains
     * any records (i.e. it was not already on the last page), this will
     * trigger g:changed.
     * @param params Any additional parameters to be passed with the request.
     * @param reset Set this to true to re-fetch the current page.
     */
    fetch: function (params, reset) {
        if (this.resourceName === null) {
            alert('Error: You must set a resourceName on your collection.');
            return;
        }

        if (reset) {
            this.offset -= this.length;
        }

        this.params = params || {};
        girder.restRequest({
            path: this.altUrl || this.resourceName,
            data: _.extend({
                'limit': this.pageLimit + 1,
                'offset': this.offset,
                'sort': this.sortField,
                'sortdir': this.sortDir
            }, this.params)
        }).done(_.bind(function (list) {
            if (list.length > this.pageLimit) {
                // This means we have more pages to display still. Pop off
                // the extra that we fetched.
                list.pop();
                this._hasMorePages = true;
            }
            else {
                this._hasMorePages = false;
            }

            this.offset += list.length;

            if (list.length > 0 || reset) {
                if (this.append) {
                    this.add(list);
                }
                else {
                    this.reset(list);
                }
            }

            this.trigger('g:changed');
        }, this));
    }
});
<commit_msg>Making girder Collections more backbone-ish<commit_after>/**
 * All collections should descend from this collection base class, which
 * provides nice utilities for pagination and sorting.
 */
girder.Collection = Backbone.Collection.extend({
    resourceName: null,

    sortField: 'name',
    sortDir: girder.SORT_ASC,

    // Number of records to fetch per page
    pageLimit: 25,
    offset: 0,

    // Alternative fetch URL
    altUrl: null,

    /**
     * Append mode can be used to append pages to the collection rather than
     * simply replacing its contents when a new page is fetched. For the sake
     * of least surprise, this property should not be changed in the definition
     * of collections, but after they are instantiated.
     */
    append: false,

    /**
     * Returns a boolean of whether or not this collection has previous pages,
     * i.e. if the offset of the current page start is > 0
     */
    hasPreviousPage: function () {
        return this.offset - this.length > 0;
    },

    /**
     * After you have called fetch() on a collection, this method will tell
     * you whether there are more pages remaining to be fetched, or if you
     * have hit the end.
     */
    hasNextPage: function () {
        return this._hasMorePages;
    },

    /**
     * Fetch the previous page of this collection, emitting g:changed when done.
     */
    fetchPreviousPage: function (params) {
        this.offset = Math.max(0, this.offset - this.length - this.pageLimit);
        this.fetch(params);
    },

    /**
     * Fetch the previous page of this collection, emitting g:changed when done.
     */
    fetchNextPage: function (params) {
        this.fetch(_.extend(this.params, params || {}));
    },

    /**
     * Return the 0-indexed page number of the current page. Add 1 to this
     * result when displaying it to the user.
     */
    pageNum: function (params) {
        return Math.ceil((this.offset - this.length) / this.pageLimit);
    },

    /**
     * Fetches the next page of this collection, replacing the existing models
     * of this collection with the requested page. If the next page contains
     * any records (i.e. it was not already on the last page), this will
     * trigger g:changed.
     * @param params Any additional parameters to be passed with the request.
     * @param reset Set this to true to re-fetch the current page.
     */
    fetch: function (params, reset) {
        if (this.resourceName === null) {
            alert('Error: You must set a resourceName on your collection.');
            return;
        }

        if (reset) {
            this.offset -= this.length;
        }

        this.params = params || {};
        girder.restRequest({
            path: this.altUrl || this.resourceName,
            data: _.extend({
                'limit': this.pageLimit + 1,
                'offset': this.offset,
                'sort': this.sortField,
                'sortdir': this.sortDir
            }, this.params)
        }).done(_.bind(function (list) {
            if (list.length > this.pageLimit) {
                // This means we have more pages to display still. Pop off
                // the extra that we fetched.
                list.pop();
                this._hasMorePages = true;
            }
            else {
                this._hasMorePages = false;
            }

            this.offset += list.length;

            list.forEach(function (item) {
                item.id = item._id;
            });

            if (list.length > 0 || reset) {
                if (this.append) {
                    this.add(list);
                }
                else {
                    this.set(list);
                }
            }

            this.trigger('g:changed');
        }, this));
    }
});
<|endoftext|>