0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-13 15:12:43 +00:00

Merge branch 'newAdmin' into v3

This commit is contained in:
Scott Tolksdorf
2017-01-28 16:36:25 -05:00
32 changed files with 1156 additions and 497 deletions

View File

@@ -1,38 +1,41 @@
var React = require('react'); const React = require('react');
var _ = require('lodash'); const _ = require('lodash');
var cx = require('classnames');
var HomebrewAdmin = require('./homebrewAdmin/homebrewAdmin.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
var Admin = React.createClass({ const BrewLookup = require('./brewLookup/brewLookup.jsx');
const AdminSearch = require('./adminSearch/adminSearch.jsx');
const InvalidBrew = require('./invalidBrew/invalidBrew.jsx');
const Admin = React.createClass({
getDefaultProps: function() { getDefaultProps: function() {
return { return {
url : "", admin_key : '',
admin_key : "",
homebrews : [],
}; };
}, },
renderNavbar : function(){
return <Nav.base>
<Nav.section>
<Nav.item icon='fa-magic' className='homebreweryLogo'>
Homebrewery Admin
</Nav.item>
</Nav.section>
</Nav.base>
},
render : function(){ render : function(){
var self = this; return <div className='admin'>
return( {this.renderNavbar()}
<div className='admin'> <main className='content'>
<BrewLookup adminKey={this.props.admin_key} />
<AdminSearch adminKey={this.props.admin_key} />
<header> <div className='dangerZone'>Danger Zone</div>
<div className='container'>
<i className='fa fa-rocket' />
naturalcrit admin
</div>
</header>
<div className='container'> <InvalidBrew adminKey={this.props.admin_key} />
</main>
<HomebrewAdmin homebrews={this.props.homebrews} admin_key={this.props.admin_key} /> </div>
</div>
</div>
);
} }
}); });

View File

@@ -1,39 +1,53 @@
@import 'naturalcrit/styles/reset.less';
@import 'naturalcrit/styles/elements.less';
@import 'naturalcrit/styles/animations.less';
@import 'naturalcrit/styles/colors.less';
@import 'naturalcrit/styles/tooltip.less';
@import 'font-awesome/css/font-awesome.css'; @import 'naturalcrit/styles/core.less';
html,body, #reactRoot{
html,body, #reactContainer, .naturalCrit{
min-height : 100%; min-height : 100%;
} }
@sidebarWidth : 250px;
body{ body{
background-color : #eee; height : 100%;
font-family : 'Open Sans', sans-serif;
color : #4b5055;
font-weight : 100;
text-rendering : optimizeLegibility;
margin : 0; margin : 0;
padding : 0; padding : 0;
height : 100%; background-color : #ddd;
font-family : 'Open Sans', sans-serif;
font-weight : 100;
color : #4b5055;
text-rendering : optimizeLegibility;
} }
.admin {
.admin{ nav {
header{
background-color : @red; background-color : @red;
font-size: 2em; .navItem{
padding : 20px 0px; background-color : @red;
color : white; }
margin-bottom: 30px; .homebreweryLogo{
i{ font-family : CodeBold;
margin-right: 30px; font-size : 12px;
color : white;
div{
margin-top : 2px;
margin-bottom : -2px;
}
}
}
h1{
margin-bottom : 10px;
font-size : 2em;
font-weight : 800;
border-bottom : 1px solid #ddd;
}
main.content{
width : 1000px;
margin : 0 auto;
padding : 50px 20px;
background-color : white;
.dangerZone{
margin : 30px 0px;
padding : 10px 20px;
background : repeating-linear-gradient(45deg, @yellow, @yellow 10px, darken(#333, 10%) 10px, darken(#333, 10%) 20px);
font-size : 1em;
font-weight : 800;
color : white;
text-transform : uppercase;
} }
} }
} }

View File

@@ -0,0 +1,86 @@
const React = require('react');
const _ = require('lodash');
const cx = require('classnames');
const request = require('superagent');
const BrewTable = require('../brewTable/brewTable.jsx');
const LIMIT = 10;
const AdminSearch = React.createClass({
getDefaultProps: function() {
return {
adminKey : '',
};
},
getInitialState: function() {
return {
totalBrews : 1,
brews: [],
searching : false,
error : null,
page : 1,
searchTerms : ''
};
},
handleSearch : function(e){
this.setState({
searchTerms : e.target.value
});
},
handlePage : function(e){
this.setState({
page : e.target.value
});
},
search : function(){
this.setState({ searching : true, error : null });
request.get(`/api/brew`)
.query({
terms : this.state.searchTerms,
limit : LIMIT,
page : this.state.page - 1
})
.set('x-homebrew-admin', this.props.adminKey)
.end((err, res) => {
if(err){
this.setState({
searching : false,
error : err && err.toString()
});
}else{
this.setState({
brews : res.body.brews,
totalBrews : res.body.total
});
}
});
},
render: function(){
return <div className='adminSearch'>
<h1>Admin Search</h1>
<div className='controls'>
<input className='search' type='text' value={this.state.searchTerms} onChange={this.handleSearch} />
<button onClick={this.search}> <i className='fa fa-search' /> search </button>
<div className='page'>
page:
<input type='text' value={this.state.page} onChange={this.handlePage} />
/ {Math.ceil(this.state.totalBrews / LIMIT)}
</div>
</div>
<BrewTable brews={this.state.brews} />
</div>
}
});
module.exports = AdminSearch;

View File

@@ -0,0 +1,17 @@
.adminSearch{
.controls{
margin-bottom : 20px;
input.search{
height : 33px;
padding : 10px;
}
.page {
float : right;
font-weight : 800;
input{
width : 20px;
}
}
}
}

View File

@@ -3,8 +3,7 @@ const _ = require('lodash');
const cx = require('classnames'); const cx = require('classnames');
const request = require('superagent'); const request = require('superagent');
const Moment = require('moment'); const BrewTable = require('../brewTable/brewTable.jsx');
const BrewLookup = React.createClass({ const BrewLookup = React.createClass({
getDefaultProps: function() { getDefaultProps: function() {
@@ -16,7 +15,8 @@ const BrewLookup = React.createClass({
return { return {
query:'', query:'',
resultBrew : null, resultBrew : null,
searching : false searching : false,
error : null
}; };
}, },
@@ -26,13 +26,14 @@ const BrewLookup = React.createClass({
}) })
}, },
lookup : function(){ lookup : function(){
this.setState({ searching : true }); this.setState({ searching : true, error : null });
request.get(`/admin/lookup/${this.state.query}`) request.get(`/admin/lookup/${this.state.query}`)
.query({ admin_key : this.props.adminKey }) .set('x-homebrew-admin', this.props.adminKey)
.end((err, res) => { .end((err, res) => {
this.setState({ this.setState({
searching : false, searching : false,
error : err && err.toString(),
resultBrew : (err ? null : res.body) resultBrew : (err ? null : res.body)
}); });
}) })
@@ -42,14 +43,31 @@ const BrewLookup = React.createClass({
if(this.state.searching) return <div className='searching'><i className='fa fa-spin fa-spinner' /></div>; if(this.state.searching) return <div className='searching'><i className='fa fa-spin fa-spinner' /></div>;
if(!this.state.resultBrew) return <div className='noBrew'>No brew found.</div>; if(!this.state.resultBrew) return <div className='noBrew'>No brew found.</div>;
return <BrewTable brews={[this.state.resultBrew ]} />
/*
const brew = this.state.resultBrew; const brew = this.state.resultBrew;
return <div className='brewRow'> return <div className='brewRow'>
<div>{brew.title}</div> <div>{brew.title}</div>
<div>{brew.authors.join(', ')}</div> <div>{brew.authors.join(', ')}</div>
<div><a href={'/edit/' + brew.editId} target='_blank'>/edit/{brew.editId}</a></div> <div><a href={'/edit/' + brew.editId} target='_blank'>{brew.editId}</a></div>
<div><a href={'/share/' + brew.shareId} target='_blank'>/share/{brew.shareId}</a></div> <div><a href={'/share/' + brew.shareId} target='_blank'>{brew.shareId}</a></div>
<div>{Moment(brew.updatedAt).fromNow()}</div> <div>{Moment(brew.updatedAt).fromNow()}</div>
<div>{brew.views}</div> <div>{brew.views}</div>
<div>
<div className='deleteButton'>
<i className='fa fa-trash' />
</div>
</div>
</div>
*/
},
renderError : function(){
if(!this.state.error) return;
return <div className='error'>
{this.state.error}
</div> </div>
}, },
@@ -60,6 +78,7 @@ const BrewLookup = React.createClass({
<button onClick={this.lookup}><i className='fa fa-search'/></button> <button onClick={this.lookup}><i className='fa fa-search'/></button>
{this.renderFoundBrew()} {this.renderFoundBrew()}
{this.renderError()}
</div> </div>
} }
}); });

View File

@@ -0,0 +1,13 @@
.brewLookup{
height : 200px;
input{
height : 33px;
margin-bottom : 20px;
padding : 0px 10px;
}
.error{
font-weight : 800;
color : @red;
}
}

View File

@@ -0,0 +1,54 @@
const React = require('react');
const _ = require('lodash');
const cx = require('classnames');
const Moment = require('moment');
//TODO: Add in delete
const BrewTable = React.createClass({
getDefaultProps: function() {
return {
brews : []
};
},
renderRows : function(){
return _.map(this.props.brews, (brew) => {
let authors = 'None.';
if(brew.authors && brew.authors.length) authors = brew.authors.join(', ');
return <tr className={cx('brewRow', {'isEmpty' : brew.text == "false"})} key={brew.shareId || brew}>
<td>{brew.title}</td>
<td>{authors}</td>
<td><a href={'/edit/' + brew.editId} target='_blank'>{brew.editId}</a></td>
<td><a href={'/share/' + brew.shareId} target='_blank'>{brew.shareId}</a></td>
<td>{Moment(brew.updatedAt).fromNow()}</td>
<td>{brew.views}</td>
<td className='deleteButton'>
<i className='fa fa-trash' />
</td>
</tr>
});
},
render: function(){
return <table className='brewTable'>
<thead>
<tr>
<th>Title</th>
<th>Authors</th>
<th>Edit Link</th>
<th>Share Link</th>
<th>Last Updated</th>
<th>Views</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{this.renderRows()}
</tbody>
</table>
}
});
module.exports = BrewTable;

View File

@@ -0,0 +1,44 @@
table.brewTable{
th{
padding : 10px;
background-color : fade(@blue, 20%);
font-weight : 800;
}
tr:nth-child(even){
background-color : fade(@green, 10%);
}
tr.isEmpty{
background-color : fade(@red, 30%);
}
td{
min-width : 100px;
padding : 10px;
text-align : center;
/*
&.preview{
position : relative;
&:hover{
.content{
display : block;
}
}
.content{
position : absolute;
display : none;
top : 100%;
left : 0px;
z-index : 1000;
max-height : 500px;
width : 300px;
padding : 30px;
background-color : white;
font-family : monospace;
text-align : left;
pointer-events : none;
}
}
*/
}
}

View File

@@ -1,8 +0,0 @@
.brewLookup{
height : 200px;
input{
height : 33px;
padding : 0px 10px;
margin-bottom: 20px;
}
}

View File

@@ -1,72 +0,0 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var request = require('superagent');
var BrewSearch = React.createClass({
getDefaultProps: function() {
return {
admin_key : ''
};
},
getInitialState: function() {
return {
searchTerm: '',
brew : null,
searching : false
};
},
search : function(){
this.setState({
searching : true
});
request.get('/homebrew/api/search?id=' + this.state.searchTerm)
.query({
admin_key : this.props.admin_key,
})
.end((err, res)=>{
console.log(err, res, res.body.brews[0]);
this.setState({
brew : res.body.brews[0],
searching : false
})
});
},
handleChange : function(e){
this.setState({
searchTerm : e.target.value
});
},
handleSearchClick : function(){
this.search();
},
renderBrew : function(){
if(!this.state.brew) return null;
return <div className='brew'>
<div>Edit id : {this.state.brew.editId}</div>
<div>Share id : {this.state.brew.shareId}</div>
</div>
},
render : function(){
return <div className='search'>
<input type='text' value={this.state.searchTerm} onChange={this.handleChange} />
<button onClick={this.handleSearchClick}>Search</button>
{this.renderBrew()}
</div>
},
});
module.exports = BrewSearch;

View File

@@ -1,172 +0,0 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var request = require('superagent');
var Moment = require('moment');
var BrewSearch = require('./brewSearch.jsx');
var BrewLookup = require('./brewLookup/brewLookup.jsx');
var HomebrewAdmin = React.createClass({
getDefaultProps: function() {
return {
admin_key : ''
};
},
getInitialState: function() {
return {
page: 0,
count : 20,
brewCache : {},
total : 0,
processingOldBrews : false
};
},
fetchBrews : function(page){
request.get('/api/search')
.query({
admin_key : this.props.admin_key,
count : this.state.count,
page : page
})
.end((err, res)=>{
if(err || !res.body || !res.body.brews) return;
this.state.brewCache[page] = res.body.brews;
this.setState({
brewCache : this.state.brewCache,
total : res.body.total,
count : res.body.count
})
})
},
componentDidMount: function() {
this.fetchBrews(this.state.page);
},
changePageTo : function(page){
if(!this.state.brewCache[page]){
this.fetchBrews(page);
}
this.setState({
page : page
})
},
clearInvalidBrews : function(){
request.get('/api/invalid')
.query({admin_key : this.props.admin_key})
.end((err, res)=>{
if(!confirm("This will remove " + res.body.count + " brews. Are you sure?")) return;
request.get('/api/invalid')
.query({admin_key : this.props.admin_key, do_it : true})
.end((err, res)=>{
alert("Done!")
});
});
},
deleteBrew : function(brewId){
if(!confirm("Are you sure you want to delete '" + brewId + "'?")) return;
request.get('/api/remove/' + brewId)
.query({admin_key : this.props.admin_key})
.end(function(err, res){
window.location.reload();
})
},
handlePageChange : function(dir){
this.changePageTo(this.state.page + dir);
},
renderPagnination : function(){
var outOf;
if(this.state.total){
outOf = this.state.page + ' / ' + Math.round(this.state.total/this.state.count);
}
return <div className='pagnination'>
<i className='fa fa-chevron-left' onClick={this.handlePageChange.bind(this, -1)}/>
{outOf}
<i className='fa fa-chevron-right' onClick={this.handlePageChange.bind(this, 1)}/>
</div>
},
renderBrews : function(){
var brews = this.state.brewCache[this.state.page] || _.times(this.state.count);
return _.map(brews, (brew)=>{
return <tr className={cx('brewRow', {'isEmpty' : brew.text == "false"})} key={brew.shareId || brew}>
<td><a href={'/edit/' + brew.editId} target='_blank'>{brew.editId}</a></td>
<td><a href={'/share/' + brew.shareId} target='_blank'>{brew.shareId}</a></td>
<td>{Moment(brew.createdAt).fromNow()}</td>
<td>{Moment(brew.updatedAt).fromNow()}</td>
<td>{Moment(brew.lastViewed).fromNow()}</td>
<td>{brew.views}</td>
<td>
<div className='deleteButton' onClick={this.deleteBrew.bind(this, brew.editId)}>
<i className='fa fa-trash' />
</div>
</td>
</tr>
});
},
renderBrewTable : function(){
return <div className='brewTable'>
<table>
<thead>
<tr>
<th>Edit Id</th>
<th>Share Id</th>
<th>Created At</th>
<th>Last Updated</th>
<th>Last Viewed</th>
<th>Views</th>
</tr>
</thead>
<tbody>
{this.renderBrews()}
</tbody>
</table>
</div>
},
render : function(){
var self = this;
return <div className='homebrewAdmin'>
<BrewLookup adminKey={this.props.admin_key} />
{/*
<h2>
Homebrews - {this.state.total}
</h2>
{this.renderPagnination()}
{this.renderBrewTable()}
<button className='clearOldButton' onClick={this.clearInvalidBrews}>
Clear Old
</button>
<BrewSearch admin_key={this.props.admin_key} />
*/}
</div>
}
});
module.exports = HomebrewAdmin;

View File

@@ -1,53 +0,0 @@
.homebrewAdmin{
margin-bottom: 80px;
.brewTable{
table{
th{
padding : 10px;
font-weight : 800;
}
tr:nth-child(even){
background-color : fade(@green, 10%);
}
tr.isEmpty{
background-color : fade(@red, 30%);
}
td{
min-width : 100px;
padding : 10px;
text-align : center;
&.preview{
position : relative;
&:hover{
.content{
display : block;
}
}
.content{
position : absolute;
display : none;
top : 100%;
left : 0px;
z-index : 1000;
max-height : 500px;
width : 300px;
padding : 30px;
background-color : white;
font-family : monospace;
text-align : left;
pointer-events : none;
}
}
}
}
}
.deleteButton{
cursor: pointer;
}
button.clearOldButton{
float : right;
}
}

View File

@@ -0,0 +1,54 @@
const React = require('react');
const _ = require('lodash');
const cx = require('classnames');
const request = require('superagent');
const BrewTable = require('../brewTable/brewTable.jsx');
const InvalidBrew = React.createClass({
getDefaultProps: function() {
return {
adminKey : '',
};
},
getInitialState: function() {
return {
brews: []
};
},
getInvalid : function(){
request.get(`/admin/invalid`)
.set('x-homebrew-admin', this.props.adminKey)
.end((err, res) => {
this.setState({
brews : res.body
});
})
},
removeInvalid : function(){
if(!this.state.brews.length) return;
if(!confirm(`Are you sure you want to remove ${this.state.brews.length} brews`)) return;
if(!confirm('Sure you are sure?')) return;
request.delete(`/admin/invalid`)
.set('x-homebrew-admin', this.props.adminKey)
.end((err, res) => {
console.log(err, res.body);
alert('Invalid brews removed!');
this.getInvalid();
})
},
render: function(){
return <div className='invalidBrew'>
<h1>Remove Invalid Brews</h1>
<div>This will removes all brews older than 3 days and shorter than a tweet.</div>
<button className='get' onClick={this.getInvalid}> Get Invalid Brews</button>
<button className='remove' disabled={this.state.brews.length == 0} onClick={this.removeInvalid}> Remove invalid Brews</button>
<BrewTable brews={this.state.brews} />
</div>
}
});
module.exports = InvalidBrew;

View File

@@ -0,0 +1,5 @@
.invalidBrew{
button{
margin: 10px 4px;
}
}

View File

@@ -37,7 +37,6 @@ const Homebrew = React.createClass({
loginPath : this.props.loginPath loginPath : this.props.loginPath
}); });
Router = CreateRouter({ Router = CreateRouter({
'/edit/:id' : <EditPage />, '/edit/:id' : <EditPage />,
'/share/:id' : <SharePage />, '/share/:id' : <SharePage />,

View File

@@ -52,6 +52,7 @@ const UserPage = React.createClass({
render : function(){ render : function(){
const brews = this.getSortedBrews(); const brews = this.getSortedBrews();
console.log('user brews', brews);
return <div className='userPage page'> return <div className='userPage page'>
<Navbar> <Navbar>

View File

@@ -7,6 +7,7 @@
"quick": "node scripts/quick.js", "quick": "node scripts/quick.js",
"build": "node scripts/build.js", "build": "node scripts/build.js",
"phb": "node scripts/phb.js", "phb": "node scripts/phb.js",
"populate": "node scripts/populate.js",
"prod": "set NODE_ENV=production&& npm run build", "prod": "set NODE_ENV=production&& npm run build",
"postinstall": "npm run build", "postinstall": "npm run build",
"start": "node server.js", "start": "node server.js",
@@ -43,6 +44,7 @@
"app-module-path": "^2.1.0", "app-module-path": "^2.1.0",
"chai": "^3.5.0", "chai": "^3.5.0",
"chai-as-promised": "^6.0.0", "chai-as-promised": "^6.0.0",
"chai-subset": "^1.4.0",
"mocha": "^3.2.0", "mocha": "^3.2.0",
"supertest": "^2.0.1", "supertest": "^2.0.1",
"supertest-as-promised": "^4.0.2" "supertest-as-promised": "^4.0.2"

22
scripts/populate.js Normal file
View File

@@ -0,0 +1,22 @@
//Populates the DB with a bunch of brews for UI testing
const _ = require('lodash');
const DB = require('../server/db.js');
const BrewData = require('../server/brew.data.js');
const BrewGen = require('../test/brew.gen.js');
return Promise.resolve()
.then(DB.connect)
.then(BrewData.removeAll)
.then(() => {
console.log('Adding random brews...');
return BrewGen.populateDB(BrewGen.random(50));
})
.then(() => {
console.log('Adding specific brews...');
return BrewGen.populateDB(BrewGen.static());
})
.then(() => {
return DB.close();
})
.catch(console.error);

View File

@@ -3,10 +3,21 @@ const router = require('express').Router();
const vitreumRender = require('vitreum/steps/render'); const vitreumRender = require('vitreum/steps/render');
const templateFn = require('../client/template.js'); const templateFn = require('../client/template.js');
const config = require('nconf'); const config = require('nconf');
const Moment = require('moment');
const mw = require('./middleware.js'); const mw = require('./middleware.js');
const BrewData = require('./brew.data.js'); const BrewData = require('./brew.data.js');
const getInvalidBrewQuery = ()=>{
return BrewData.model.find({
'$where' : "this.text.length < 140",
createdAt: {
$lt: Moment().subtract(3, 'days').toDate()
}
}).select({ text : false });
}
router.get('/admin', mw.adminLogin, (req, res, next) => { router.get('/admin', mw.adminLogin, (req, res, next) => {
return vitreumRender('admin', templateFn, { return vitreumRender('admin', templateFn, {
url : req.originalUrl, url : req.originalUrl,
@@ -19,12 +30,29 @@ router.get('/admin', mw.adminLogin, (req, res, next) => {
}); });
//Removes all empty brews that are older than 3 days and that are shorter than a tweet //Removes all empty brews that are older than 3 days and that are shorter than a tweet
router.get('/admin/invalid', mw.adminOnly, (req, res, next)=>{
getInvalidBrewQuery().exec()
.then((brews) => {
return res.json(brews);
})
.catch(next);
});
router.delete('/admin/invalid', mw.adminOnly, (req, res, next)=>{ router.delete('/admin/invalid', mw.adminOnly, (req, res, next)=>{
BrewData.removeInvalid(!!req.query.do_it) getInvalidBrewQuery().remove()
.then((removedCount) => { .then(()=>{
return res.join({ return res.status(200).send();
count : removedCount })
}); .catch(next);
});
router.get('/admin/lookup/:search', mw.adminOnly, (req, res, next) => {
BrewData.get({ $or:[
//Searches for partial matches on either edit or share
{editId : { "$regex": req.params.search, "$options": "i" }},
{shareId : { "$regex": req.params.search, "$options": "i" }},
]})
.then((brews) => {
return res.json(brews);
}) })
.catch(next); .catch(next);
}); });

View File

@@ -6,10 +6,25 @@ const mw = require('./middleware.js');
//Search //Search
router.get('/api/brew', (req, res, next) => { router.get('/api/brew', (req, res, next) => {
const opts = _.pick(req.query, ['limit', 'sort', 'page']);
//TODO BrewData.termSearch(req.query.terms, opts, req.admin)
.then((result) => {
return res.status(200).json(result);
})
.catch(next);
});
//User
router.get('/api/user/:username', (req, res, next) => {
const fullAccess = req.admin ||
!!(req.account && req.params.username == req.account.username);
BrewData.userSearch(req.params.username, fullAccess)
.then((result) => {
return res.status(200).json(result);
})
.catch(next);
}); });
//Get //Get

View File

@@ -21,7 +21,8 @@ const BrewSchema = mongoose.Schema({
createdAt : { type: Date, default: Date.now }, createdAt : { type: Date, default: Date.now },
updatedAt : { type: Date, default: Date.now}, updatedAt : { type: Date, default: Date.now},
lastViewed : { type: Date, default: Date.now}, lastViewed : { type: Date, default: Date.now},
views : {type:Number, default:0} views : {type:Number, default:0},
version : {type: Number, default:1}
}, { }, {
versionKey: false, versionKey: false,
toJSON : { toJSON : {
@@ -32,6 +33,9 @@ const BrewSchema = mongoose.Schema({
} }
}); });
//Index these fields for fast text searching
BrewSchema.index({ title: "text", description: "text" });
BrewSchema.methods.increaseView = function(){ BrewSchema.methods.increaseView = function(){
this.views = this.views + 1; this.views = this.views + 1;
return this.save(); return this.save();
@@ -39,9 +43,6 @@ BrewSchema.methods.increaseView = function(){
const Brew = mongoose.model('Brew', BrewSchema); const Brew = mongoose.model('Brew', BrewSchema);
const BrewData = { const BrewData = {
schema : BrewSchema, schema : BrewSchema,
model : Brew, model : Brew,
@@ -65,6 +66,9 @@ const BrewData = {
delete newBrew.shareId; delete newBrew.shareId;
delete newBrew.editId; delete newBrew.editId;
brew = _.merge(brew, newBrew, { updatedAt : Date.now() }); brew = _.merge(brew, newBrew, { updatedAt : Date.now() });
brew.markModified('authors');
brew.markModified('systems');
return brew.save(); return brew.save();
}); });
}, },
@@ -89,30 +93,8 @@ const BrewData = {
getByEdit : (editId) => { getByEdit : (editId) => {
return BrewData.get({ editId }); return BrewData.get({ editId });
}, },
//Removes all empty brews that are older than 3 days and that are shorter than a tweet
removeInvalid : (force = false) => {
const invalidBrewQuery = Brew.find({
'$where' : "this.text.length < 140",
createdAt: {
$lt: Moment().subtract(3, 'days').toDate()
}
});
if(force) return invalidBrewQuery.remove().exec();
return invalidBrewQuery.exec()
.then((objs) => {
return objs.length;
});
},
search : (query, req={}) => {
//defaults with page and count
//returns a non-text version of brews
//assume sanatized ?
return Promise.resolve([]);
},
}; };
module.exports = BrewData; const BrewSearch = require('./brew.search.js')(Brew);
module.exports = _.merge(BrewData, BrewSearch);

66
server/brew.search.js Normal file
View File

@@ -0,0 +1,66 @@
const _ = require('lodash');
module.exports = (Brew) => {
const cmds = {
termSearch : (terms='', opts, fullAccess) => {
let query = {};
if(terms){
query = {$text: {
//Wrap terms in quotes to perform an AND operation
$search: _.map(terms.split(' '), (term)=>{
return `\"${term}\"`;
}).join(' '),
$caseSensitive : false
}};
}
return cmds.search(query, opts, fullAccess);
},
userSearch : (username, fullAccess) => {
const query = {
authors : username
};
return cmds.search(query, {}, fullAccess);
},
search : (queryObj={}, options={}, fullAccess = true) => {
const opts = _.merge({
limit : 25,
page : 0,
sort : {}
}, options);
opts.limit = _.toNumber(opts.limit);
opts.page = _.toNumber(opts.page);
let filter = {
text : 0
};
if(!fullAccess){
filter.editId = 0;
queryObj.published = true;
}
const searchQuery = Brew
.find(queryObj)
.sort(opts.sort)
.select(filter)
.limit(opts.limit)
.skip(opts.page * opts.limit)
.lean()
.exec();
const countQuery = Brew.count(queryObj).exec();
return Promise.all([searchQuery, countQuery])
.then((result) => {
return {
brews : result[0],
total : result[1]
}
});
}
};
return cmds;
};

View File

@@ -8,14 +8,14 @@ module.exports = {
connect : ()=>{ connect : ()=>{
return new Promise((resolve, reject)=>{ return new Promise((resolve, reject)=>{
if(mongoose.connection.readyState == 1){ if(mongoose.connection.readyState == 1){
log.info('DB already connected'); log.warn('DB already connected');
return resolve(); return resolve();
} }
mongoose.connect(dbPath, mongoose.connect(dbPath,
(err) => { (err) => {
if(err){ if(err){
log.info('Error : Could not connect to a Mongo Database.'); log.error('Error : Could not connect to a Mongo Database.');
log.info(' If you are running locally, make sure mongodb.exe is running.'); log.error(' If you are running locally, make sure mongodb.exe is running.');
return reject(err); return reject(err);
} }
log.info('DB connected.'); log.info('DB connected.');
@@ -24,5 +24,13 @@ module.exports = {
); );
}); });
}, },
close : ()=>{
return new Promise((resolve, reject) => {
mongoose.connection.close(()=>{
log.info('DB connection closed.');
return resolve();
});
});
},
instance : mongoose instance : mongoose
} }

View File

@@ -16,7 +16,8 @@ const Middleware = {
return next(); return next();
}, },
admin : (req, res, next) => { admin : (req, res, next) => {
if(req.query.admin_key === config.get('admin:key')){ req.admin = false;
if(req.headers['x-homebrew-admin'] === config.get('admin:key')){
req.admin = true; req.admin = true;
} }
return next(); return next();
@@ -44,6 +45,7 @@ const Middleware = {
}, },
//TODO: REMOVE
//Loaders //Loaders
loadBrew : (req, res, next) => { loadBrew : (req, res, next) => {
BrewData.getByEdit(req.params.editId) BrewData.getByEdit(req.params.editId)

View File

@@ -16,7 +16,6 @@ Store.init = (state)=>{
Store.getLoginPath = ()=>{ Store.getLoginPath = ()=>{
let path = State.loginPath; let path = State.loginPath;
if(typeof window !== 'undefined'){ if(typeof window !== 'undefined'){
console.log('yo here');
path = `${path}?redirect=${encodeURIComponent(window.location.href)}`; path = `${path}?redirect=${encodeURIComponent(window.location.href)}`;
} }
return path; return path;

View File

@@ -76,4 +76,7 @@
font-size: 0.8em; font-size: 0.8em;
line-height : 1.5em; line-height : 1.5em;
} }
.thumbnail.field{
}
} }

90
test/admin.test.js Normal file
View File

@@ -0,0 +1,90 @@
const testing = require('./test.init.js');
const request = require('supertest-as-promised');
const config = require('nconf');
const app = require('app.js');
const DB = require('db.js');
const BrewData = require('brew.data.js');
const Error = require('error.js');
let brewA = {
title : 'good title',
text : 'original text',
authors : ['your_dm']
};
describe('Admin API', ()=>{
before('Connect DB', DB.connect);
describe('Brew Lookup', ()=>{
before('Clear DB', BrewData.removeAll);
before('Create brew', ()=>{
return BrewData.create(brewA)
.then((brew)=>{ brewA = brew; });
});
it('throws an error if not admin', ()=>{
return request(app)
.get(`/admin/lookup/${brewA.editId}`)
.expect(401);
});
it('looks up a brew based on the share id', () => {
return request(app)
.get(`/admin/lookup/${brewA.shareId}`)
.set('x-homebrew-admin', config.get('admin:key'))
.expect(200)
.then((res) => {
const brew = res.body;
brew.should.have.property('editId').equal(brewA.editId);
brew.should.have.property('shareId').equal(brewA.shareId);
brew.should.have.property('text').equal(brewA.text);
});
});
it('looks up a brew based on the edit id', () => {
return request(app)
.get(`/admin/lookup/${brewA.editId}`)
.set('x-homebrew-admin', config.get('admin:key'))
.expect(200)
.then((res) => {
const brew = res.body;
brew.should.have.property('editId').equal(brewA.editId);
brew.should.have.property('shareId').equal(brewA.shareId);
brew.should.have.property('text').equal(brewA.text);
});
});
it('looks up a brew based on a partial id', () => {
const query = brewA.editId.substring(0, brewA.editId.length -2);
return request(app)
.get(`/admin/lookup/${query}`)
.set('x-homebrew-admin', config.get('admin:key'))
.expect(200)
.then((res) => {
const brew = res.body;
brew.should.have.property('editId').equal(brewA.editId);
brew.should.have.property('shareId').equal(brewA.shareId);
brew.should.have.property('text').equal(brewA.text);
});
});
it('throws an error if it can not find a brew', ()=>{
return request(app)
.get(`/admin/lookup/BADID`)
.set('x-homebrew-admin', config.get('admin:key'))
.expect(404);
});
});
describe('Invalid Brew', ()=>{
before('Clear DB', BrewData.removeAll);
before('Create brew', ()=>{
return BrewData.create(brewA)
.then((brew)=>{ brewA = brew; });
});
});
});

View File

@@ -1,112 +1,246 @@
const testing = require('./test.init.js'); const Test = require('./test.init.js');
const _ = require('lodash');
const request = require('supertest-as-promised'); const request = require('supertest-as-promised');
const jwt = require('jwt-simple');
const config = require('nconf'); const config = require('nconf');
const app = require('app.js'); const app = require('app.js');
const DB = require('db.js'); const DB = require('db.js');
const BrewData = require('brew.data.js'); const BrewData = require('brew.data.js');
const BrewGen = require('./brew.gen.js');
const Error = require('error.js'); const Error = require('error.js');
const apiPath = '/api/brew';
let session_token; const UserX = { username : 'userX' };
const test_user = { const UserA = { username : 'userA' };
username : 'cool guy' let UserXToken, UserAToken;
};
let storedBrew = {
title : 'good title',
text : 'original text',
authors : ['your_dm']
};
describe('Brew API', () => { describe('Brew API', () => {
before('Connect DB', DB.connect);
before('Clear DB', BrewData.removeAll);
before('Create session token', () => { before('Create session token', () => {
session_token = jwt.encode(test_user, config.get('jwt_secret')); UserXToken = Test.getSessionToken(UserX);
}); UserAToken = Test.getSessionToken(UserA);
before('Create brew', ()=>{
return BrewData.create(storedBrew)
.then((brew)=>{ storedBrew = brew; });
}); });
describe('CRUD', ()=>{
before('Connect DB', DB.connect);
before('Clear DB', BrewData.removeAll);
before('Populate brews', ()=>{
return BrewGen.populateDB(BrewGen.static());
});
describe('Create', () => {
it('creates a new brew', () => {
return request(app)
.post(`/api/brew`)
.send({ text : 'Brew Text' })
.expect(200)
.then((res) => {
const brew = res.body;
brew.should.have.property('editId').that.is.a('string');
brew.should.have.property('shareId').that.is.a('string');
brew.should.have.property('text').equal('Brew Text');
brew.should.not.have.property('_id');
});
});
describe('Create', () => { it('creates a new brew with a session author', () => {
it('creates a new brew', () => { return request(app)
return request(app) .post(`/api/brew`)
.post(apiPath) .set('Cookie', `nc_session=${UserXToken}`)
.send({ text : 'Brew Text' }) .send({ text : 'Brew Text' })
.expect(200) .expect(200)
.then((res) => { .then((res) => {
const brew = res.body; const brew = res.body;
brew.should.have.property('editId').that.is.a('string'); brew.should.have.property('authors').include(UserX.username);
brew.should.have.property('shareId').that.is.a('string'); });
brew.should.have.property('text').equal('Brew Text'); });
brew.should.not.have.property('_id');
});
}); });
it('creates a new brew with a session author', () => { describe('Update', () => {
return request(app) it('updates an existing brew', () => {
.post(apiPath) const storedBrew = BrewGen.get('BrewA');
.set('Cookie', `nc_session=${session_token}`) return request(app)
.send({ text : 'Brew Text' }) .put(`/api/brew/${storedBrew.editId}`)
.expect(200) .send({ text : 'New Text' })
.then((res) => { .expect(200)
const brew = res.body; .then((res) => {
brew.should.have.property('authors').include(test_user.username); const brew = res.body;
}); brew.should.have.property('editId').equal(storedBrew.editId);
}); brew.should.have.property('text').equal('New Text');
}); brew.should.have.property('authors').include(storedBrew.authors[0]);
brew.should.not.have.property('_id');
});
});
describe('Update', () => { it('adds the user as author', () => {
it('updates an existing brew', () => { const storedBrew = BrewGen.get('BrewA');
return request(app) return request(app)
.put(`${apiPath}/${storedBrew.editId}`) .put(`/api/brew/${storedBrew.editId}`)
.send({ text : 'New Text' }) .set('Cookie', `nc_session=${UserXToken}`)
.expect(200) .send({ text : 'New Text' })
.then((res) => { .expect(200)
const brew = res.body; .then((res) => {
brew.should.have.property('editId').equal(storedBrew.editId); const brew = res.body;
brew.should.have.property('text').equal('New Text'); brew.should.have.property('authors').include(UserX.username);
brew.should.have.property('authors').include('your_dm'); brew.should.have.property('authors').include(storedBrew.authors[0]);
brew.should.not.have.property('_id'); });
}); });
it('should throw error on bad edit id', ()=>{
const storedBrew = BrewGen.get('BrewA');
return request(app)
.put(`/api/brew/BADEDITID`)
.send({ text : 'New Text' })
.expect(404)
});
}); });
it('adds the user as author', () => { describe('Remove', () => {
return request(app) it('should removes a brew', ()=>{
.put(`${apiPath}/${storedBrew.editId}`) const storedBrew = BrewGen.get('BrewA');
.set('Cookie', `nc_session=${session_token}`) return request(app)
.send({ text : 'New Text' }) .del(`/api/brew/${storedBrew.editId}`)
.expect(200) .send()
.then((res) => { .expect(200)
const brew = res.body; .then(() => {
brew.should.have.property('authors').include(test_user.username); BrewData.getByEdit(storedBrew.editId)
brew.should.have.property('authors').include('your_dm'); .then(() => { throw 'Brew found when one should not have been'; })
}); .catch((err) => {
err.should.be.instanceof(Error.noBrew);
})
});
});
}); });
it('should throw error on bad edit id', ()=>{ })
return request(app)
.put(`${apiPath}/BADEDITID`)
.send({ text : 'New Text' })
.expect(404)
});
});
describe('Remove', () => {
it('should removes a brew', ()=>{ describe('Search', () => {
before('Connect DB', DB.connect);
before('Clear DB', BrewData.removeAll);
before('Populate brews', ()=>{
return BrewGen.populateDB(BrewGen.static());
});
it('should be able to search for all published brews', ()=>{
return request(app) return request(app)
.del(`${apiPath}/${storedBrew.editId}`) .get(`/api/brew`)
.query({})
.send() .send()
.expect(200) .expect(200)
.then(() => { .then((res) => {
BrewData.getByEdit(storedBrew.editId) const result = res.body;
.then(() => { throw 'Brew found when one should not have been'; }) result.total.should.be.equal(2);
.catch((err) => { result.brews.should.have.brews('BrewB','BrewD');
err.should.be.instanceof(Error.noBrew); result.brews[0].should.not.have.property('editId');
}) });
});
it('should be able to search for brews with given terms', ()=>{
return request(app)
.get(`/api/brew`)
.query({
terms : '5e ranger'
})
.send()
.expect(200)
.then((res) => {
const result = res.body;
result.total.should.be.equal(1);
result.brews.should.have.brews('BrewD');
});
});
it('should be able to sort the search', ()=>{
return request(app)
.get(`/api/brew`)
.query({
sort : { views : 1}
})
.send()
.expect(200)
.then((res) => {
const result = res.body;
result.total.should.be.equal(2);
result.brews[0].should.be.brew('BrewD');
result.brews[1].should.be.brew('BrewB');
});
});
it('should use pagniation on the search', ()=>{
return request(app)
.get(`/api/brew`)
.query({
limit : 1,
page : 1,
sort : { views : -1}
})
.send()
.expect(200)
.then((res) => {
const result = res.body;
result.total.should.be.equal(2);
result.brews[0].should.be.brew('BrewD');
})
});
it('should return all brews and editIds if admin', ()=>{
return request(app)
.get(`/api/brew`)
.query({})
.set('x-homebrew-admin', config.get('admin:key'))
.send()
.expect(200)
.then((res) => {
const result = res.body;
const brewCount = _.size(BrewGen.static());
result.total.should.be.equal(brewCount);
result.brews.length.should.be.equal(brewCount);
result.brews[0].should.have.property('editId');
});
});
});
describe('User', () => {
before('Connect DB', DB.connect);
before('Clear DB', BrewData.removeAll);
before('Populate brews', ()=>{
return BrewGen.populateDB(BrewGen.static());
});
it('should be able to query brews for a specific user', ()=>{
return request(app)
.get(`/api/user/userA`)
.send()
.expect(200)
.then((res) => {
const result = res.body;
result.total.should.be.equal(1);
result.brews.length.should.be.equal(1);
result.brews.should.have.brews('BrewB');
result.brews[0].should.not.have.property('editId');
});
});
it('should have full access if loggedin user is queried user', ()=>{
return request(app)
.get(`/api/user/userA`)
.set('Cookie', `nc_session=${UserAToken}`)
.send()
.expect(200)
.then((res) => {
const result = res.body;
result.total.should.be.equal(3);
result.brews.length.should.be.equal(3);
result.brews.should.have.brews('BrewA', 'BrewB', 'BrewC');
result.brews[0].should.have.property('editId');
});
});
it('should have full access if admin', ()=>{
return request(app)
.get(`/api/user/userA`)
.set('x-homebrew-admin', config.get('admin:key'))
.send()
.expect(200)
.then((res) => {
const result = res.body;
result.total.should.be.equal(3);
result.brews.length.should.be.equal(3);
result.brews.should.have.brews('BrewA', 'BrewB', 'BrewC');
result.brews[0].should.have.property('editId');
}); });
}); });
}); });

111
test/brew.gen.js Normal file
View File

@@ -0,0 +1,111 @@
const _ = require('lodash');
const BrewData = require('../server/brew.data.js');
let PopulatedBrews = {};
module.exports = {
//TODO: Add in a generator for old brews to test the old rendering code
random : (num = 20)=>{
return _.times(num, ()=>{
//TODO: Build better generator
return {
title : 'BrewA',
description : '',
text : '',
authors : _.sampleSize(['userA','userB','userC','userD'], _.random(0, 3)),
systems : _.sampleSize(['5e', '4e', '3.5e', 'Pathfinder'], _.random(0,2)),
views : _.random(0,1000),
published : !!_.random(0,1)
};
});
},
static : () => {
return {
BrewA : {
title : 'Brew-Alpha',
description : 'fancy',
authors : ['userA'],
systems : [],
views : 12,
published : false
},
BrewB : {
title : 'Brew-Beta',
description : 'very fancy',
authors : ['userA'],
systems : [],
views : 7,
published : true
},
BrewC : {
title : 'Brew-Charlie',
description : 'test',
authors : ['userA', 'userB'],
systems : [],
views : 0,
published : false
},
BrewD : {
title : 'Brew-Delta',
description : 'test super amazing brew for 5e. Geared for Rangers.',
authors : ['userC'],
systems : [],
views : 1,
published : true
}
};
},
populateDB : (brewCollection)=>{
PopulatedBrews = {};
return Promise.all(_.map(brewCollection, (brewData, id) => {
return BrewData.create(brewData)
.then((brew)=>{
PopulatedBrews[id] = brew;
});
})
);
},
get : (brewId) => {
return PopulatedBrews[brewId]
},
chaiPlugin : (chai, utils) => {
chai.Assertion.addMethod('brews', function(...brewIds){
new chai.Assertion(this._obj).to.be.instanceof(Array);
const valid = _.every(brewIds, (brewId) => {
const storedBrew = PopulatedBrews[brewId];
if(!storedBrew) return false;
return _.some(this._obj, (brew)=>{
return brew.shareId == storedBrew.shareId &&
brew.title == storedBrew.title &&
brew.views == storedBrew.views;
});
});
this.assert(
valid,
`expect #{this} to have brews ${brewIds.join(', ')}`,
`expect #{this} to not have brews ${brewIds.join(', ')}`
)
});
chai.Assertion.addMethod('brew', function(brewId){
new chai.Assertion(this._obj).to.be.instanceof(Object);
const brew = this._obj;
const storedBrew = PopulatedBrews[brewId];
const valid = storedBrew &&
brew.shareId == storedBrew.shareId &&
brew.title == storedBrew.title &&
brew.views == storedBrew.views;
this.assert(
valid,
`expect #{this} to be brew ${brewId}`,
`expect #{this} to not be brew ${brewId}`
)
});
}
};

View File

@@ -15,8 +15,6 @@ const requestHandler = (req, res) => {
}; };
console.log(config.get('admin:key'));
const test_user = { const test_user = {
username : 'cool guy' username : 'cool guy'
}; };
@@ -105,8 +103,8 @@ describe('Middleware', () => {
it('should detect when you use the admin key', () => { it('should detect when you use the admin key', () => {
app.use(mw.admin); app.use(mw.admin);
app.use(requestHandler) app.use(requestHandler)
return request(app).get(`/?admin_key=${config.get('admin:key')}`) return request(app).get('/')
.send() .set('x-homebrew-admin', config.get('admin:key'))
.expect(200) .expect(200)
.then((res) => { .then((res) => {
const req = res.body; const req = res.body;
@@ -118,7 +116,8 @@ describe('Middleware', () => {
app.use(mw.adminOnly); app.use(mw.adminOnly);
app.get(requestHandler); app.get(requestHandler);
app.use(Error.expressHandler); app.use(Error.expressHandler);
return request(app).get(`/?admin_key=BADKEY`) return request(app).get('/')
.set('x-homebrew-admin', 'BADADMIN')
.send() .send()
.expect(401); .expect(401);
}); });

View File

@@ -0,0 +1,186 @@
const Test = require('./test.init.js');
const _ = require('lodash');
const DB = require('db.js');
const BrewData = require('brew.data.js');
const BrewGen = require('./brew.gen.js');
//const Error = require('error.js');
describe('Brew Search', () => {
before('Connect DB', DB.connect);
before('Clear DB', BrewData.removeAll);
before('Populate brews', ()=>{
return BrewGen.populateDB(BrewGen.static());
});
describe('Searching', ()=>{
it('should return a total and a brew array', ()=>{
return BrewData.search()
.then((result) => {
result.total.should.be.a('number');
result.brews.should.be.an('array');
})
});
it('should be able to search for all brews', ()=>{
return BrewData.search()
.then((result) => {
const brewCount = _.size(BrewGen.static());
result.total.should.be.equal(brewCount);
result.brews.length.should.be.equal(brewCount);
})
});
});
describe('Pagniation', () => {
it('should return the exact number of brews based on limit', () => {
return BrewData.search({}, {
limit : 2
})
.then((result) => {
result.total.should.be.equal(_.size(BrewGen.static()));
result.brews.length.should.be.equal(2);
})
});
it('should return the correct pages when specified', () => {
return BrewData.search({}, {
limit : 2,
page : 1,
sort : { views : 1 }
})
.then((result) => {
result.brews.should.have.brews('BrewA', 'BrewB');
})
});
it('should return a partial list if on the last page', () => {
return BrewData.search({}, {
limit : 3,
page : 1
})
.then((result) => {
result.brews.length.should.be.equal(1);
});
});
});
describe('Sorting', ()=>{
it('should sort ASC', () => {
return BrewData.search({}, {
sort : { views : 1 }
})
.then((result) => {
result.brews[0].should.be.brew('BrewC');
result.brews[1].should.be.brew('BrewD');
result.brews[2].should.be.brew('BrewB');
result.brews[3].should.be.brew('BrewA');
})
});
it('should sort DESC', () => {
return BrewData.search({}, {
sort : { views : -1 }
})
.then((result) => {
result.brews[0].should.be.brew('BrewA');
result.brews[1].should.be.brew('BrewB');
result.brews[2].should.be.brew('BrewD');
result.brews[3].should.be.brew('BrewC');
})
});
});
describe('Permissions', () => {
it('should only fetch published brews', () => {
return BrewData.search({}, {}, false)
.then((result) => {
result.total.should.be.equal(2);
result.brews.should.have.brews('BrewB', 'BrewD');
})
});
it('fetched brews should not have text or editId', () => {
return BrewData.search({}, {}, false)
.then((result) => {
result.brews[0].should.not.have.property('text');
result.brews[0].should.not.have.property('editId');
})
});
it('if full access, brews should have editid, but no text', () => {
return BrewData.search({}, {}, true)
.then((result) => {
result.brews[0].should.not.have.property('text');
result.brews[0].should.have.property('editId');
})
});
});
describe('Term Search', ()=>{
it('should search brews based on title', () => {
return BrewData.termSearch('Charlie')
.then((result) => {
result.total.should.be.equal(1);
result.brews.should.have.brews('BrewC');
})
});
it('should search brews based on description', () => {
return BrewData.termSearch('fancy')
.then((result) => {
result.total.should.be.equal(2);
result.brews.should.have.brews('BrewA', 'BrewB');
})
});
it('should search brews based on multiple terms', () => {
return BrewData.termSearch('ranger 5e')
.then((result) => {
result.total.should.be.equal(1);
result.brews.should.have.brews('BrewD');
})
});
it('should perform an AND operation on the provided terms', () => {
return BrewData.termSearch('Brew Delta GARBAGE')
.then((result) => {
result.total.should.be.equal(0);
});
});
it('should search brews based on a combination of both', () => {
return BrewData.termSearch('Brew Beta fancy')
.then((result) => {
result.total.should.be.equal(1);
result.brews.should.have.brews('BrewB');
});
});
it('should not worry about the case of the terms', () => {
return BrewData.termSearch('FANCY')
.then((result) => {
result.total.should.be.equal(2);
result.brews.should.have.brews('BrewA', 'BrewB');
});
});
});
describe('User Search', ()=>{
it('should return brews just for a single user', () => {
return BrewData.userSearch('userA')
.then((result) => {
result.total.should.be.equal(3);
result.brews.should.have.brews('BrewA', 'BrewB', 'BrewC');
});
});
it('should return nothing if provided a non-exsistent user', () => {
return BrewData.userSearch('userXYZ')
.then((result) => {
result.total.should.be.equal(0);
});
});
});
});

View File

@@ -7,10 +7,18 @@ const config = require('nconf')
.file('environment', { file: `config/${process.env.NODE_ENV}.json` }) .file('environment', { file: `config/${process.env.NODE_ENV}.json` })
.file('defaults', { file: 'config/default.json' }); .file('defaults', { file: 'config/default.json' });
const should = require('chai').use(require('chai-as-promised')).should(); const Chai = require('chai')
.use(require('chai-as-promised'))
.use(require('chai-subset'))
.use(require('./brew.gen.js').chaiPlugin);
const log = require('loglevel'); const log = require('loglevel');
log.setLevel(config.get('log_level')); log.setLevel(config.get('log_level'));
const jwt = require('jwt-simple');
module.exports = { module.exports = {
should: should should: Chai.should(),
getSessionToken : (userInfo) => {
return jwt.encode(userInfo, config.get('jwt_secret'));
}
}; };