Skip to content
Snippets Groups Projects
Commit c8dba788 authored by M9713's avatar M9713
Browse files

Add te-directories

parent fa9c943c
No related branches found
No related tags found
No related merge requests found
Pipeline #509215 canceled
Showing
with 11806 additions and 0 deletions
version: 2
jobs:
build:
docker:
- image: circleci/node:8.5.0
- image: circleci/mongo:3.4.9
steps:
- checkout
- run:
name: Start app and test
command: |
yarn
yarn run start & sleep 5
curl http://localhost:3000/api/tags
yarn run test
- run:
name: Trigger integration tests
command: curl -X POST "https://circleci.com/api/v1.1/project/github/anishkny/realworld-e2e-test?circle-token=$CIRCLE_CI_TOKEN"
.git
.gitignore
.gitlab-ci.yml
README.md
node_modules
project-logo.png
back-up-system
# Logs
logs
*.log
.DS_Store
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
node_modules
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
.idea
# used only in local development
Dockerfile-dev
docker-compose-dev.yml
stages:
- build_image
- deploy_container
- delete_image
build-image:
stage: build_image
tags:
- mylly-runner
script:
- IMAGENAME=conduit-backend
- VERSIONED_IMAGENAME=$IMAGENAME:$(git rev-parse HEAD)
- docker image build -t $VERSIONED_IMAGENAME .
- docker image tag $VERSIONED_IMAGENAME $IMAGENAME:latest
- docker image ls
deploy-containers:
stage: deploy_container
tags:
- mylly-runner
script:
- docker container stop backend-container
- docker container rm backend-container
- docker run -d --net conduit-net -e MONGODB_URI=mongodb://mongo-container/conduit -e NODE_ENV=production -e SECRET=conduit --name backend-container conduit-backend
delete-image:
stage: delete_image
tags:
- mylly-runner
script:
- IMAGENAME=conduit-backend
# Delete image built five commits ago to leave five newest images
- VERSIONED_IMAGENAME=$IMAGENAME:$(git rev-parse HEAD~5)
- docker image rm $VERSIONED_IMAGENAME || echo "Failed to delete"
language: node_js
node_js: "8"
sudo: required
services: mongodb
install: yarn
before_script: yarn start & sleep 5
script: yarn test
# Choose base image (maybe use node 14 with fewer warnings??) (changed from node:16-alpine)
FROM node:16
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
COPY package*.json ./
RUN npm install
# Bundle app source
COPY . .
# Expose port
EXPOSE 3000
# Run app with node.js
CMD [ "node", "app.js" ]
# ![Node/Express/Mongoose Example App](project-logo.png)
[![Build Status](https://travis-ci.org/anishkny/node-express-realworld-example-app.svg?branch=master)](https://travis-ci.org/anishkny/node-express-realworld-example-app)
> ### Example Node (Express + Mongoose) codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the [RealWorld](https://github.com/gothinkster/realworld-example-apps) API spec.
<a href="https://thinkster.io/tutorials/node-json-api" target="_blank"><img width="454" src="https://raw.githubusercontent.com/gothinkster/realworld/master/media/learn-btn-hr.png" /></a>
This repo is functionality complete — PRs and issues welcome!
# Getting started
To get the Node server running locally:
- Clone this repo
- `npm install` to install all required dependencies
- Install MongoDB Community Edition ([instructions](https://docs.mongodb.com/manual/installation/#tutorials)) and run it by executing `mongod`
- `npm run dev` to start the local server
Alternately, to quickly try out this repo in the cloud, you can [![Remix on Glitch](https://cdn.glitch.com/2703baf2-b643-4da7-ab91-7ee2a2d00b5b%2Fremix-button.svg)](https://glitch.com/edit/#!/remix/realworld)
# Code Overview
## Dependencies
- [expressjs](https://github.com/expressjs/express) - The server for handling and routing HTTP requests
- [express-jwt](https://github.com/auth0/express-jwt) - Middleware for validating JWTs for authentication
- [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) - For generating JWTs used by authentication
- [mongoose](https://github.com/Automattic/mongoose) - For modeling and mapping MongoDB data to javascript
- [mongoose-unique-validator](https://github.com/blakehaswell/mongoose-unique-validator) - For handling unique validation errors in Mongoose. Mongoose only handles validation at the document level, so a unique index across a collection will throw an exception at the driver level. The `mongoose-unique-validator` plugin helps us by formatting the error like a normal mongoose `ValidationError`.
- [passport](https://github.com/jaredhanson/passport) - For handling user authentication
- [slug](https://github.com/dodo/node-slug) - For encoding titles into a URL-friendly format
## Application Structure
- `app.js` - The entry point to our application. This file defines our express server and connects it to MongoDB using mongoose. It also requires the routes and models we'll be using in the application.
- `config/` - This folder contains configuration for passport as well as a central location for configuration/environment variables.
- `routes/` - This folder contains the route definitions for our API.
- `models/` - This folder contains the schema definitions for our Mongoose models.
## Error Handling
In `routes/api/index.js`, we define a error-handling middleware for handling Mongoose's `ValidationError`. This middleware will respond with a 422 status code and format the response to have [error messages the clients can understand](https://github.com/gothinkster/realworld/blob/master/API.md#errors-and-status-codes)
## Authentication
Requests are authenticated using the `Authorization` header with a valid JWT. We define two express middlewares in `routes/auth.js` that can be used to authenticate requests. The `required` middleware configures the `express-jwt` middleware using our application's secret and will return a 401 status code if the request cannot be authenticated. The payload of the JWT can then be accessed from `req.payload` in the endpoint. The `optional` middleware configures the `express-jwt` in the same way as `required`, but will *not* return a 401 status code if the request cannot be authenticated.
<br />
[![Brought to you by Thinkster](https://raw.githubusercontent.com/gothinkster/realworld/master/media/end.png)](https://thinkster.io)
var http = require('http'),
path = require('path'),
methods = require('methods'),
express = require('express'),
bodyParser = require('body-parser'),
session = require('express-session'),
cors = require('cors'),
passport = require('passport'),
errorhandler = require('errorhandler'),
mongoose = require('mongoose');
var isProduction = process.env.NODE_ENV === 'production';
// Create global app object
var app = express();
app.use(cors());
// Normal express config defaults
app.use(require('morgan')('dev'));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(require('method-override')());
app.use(express.static(__dirname + '/public'));
app.use(session({ secret: 'conduit', cookie: { maxAge: 60000 }, resave: false, saveUninitialized: false }));
if (!isProduction) {
app.use(errorhandler());
}
if(isProduction){
mongoose.connect(process.env.MONGODB_URI);
} else {
mongoose.connect('mongodb://localhost/conduit');
mongoose.set('debug', true);
}
require('./models/User');
require('./models/Article');
require('./models/Comment');
require('./config/passport');
app.use(require('./routes'));
/// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
/// error handlers
// development error handler
// will print stacktrace
if (!isProduction) {
app.use(function(err, req, res, next) {
console.log(err.stack);
res.status(err.status || 500);
res.json({'errors': {
message: err.message,
error: err
}});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.json({'errors': {
message: err.message,
error: {}
}});
});
// finally, let's start our server...
var server = app.listen( process.env.PORT || 3000, function(){
console.log('Listening on port ' + server.address().port);
});
module.exports = {
secret: process.env.NODE_ENV === 'production' ? process.env.SECRET : 'secret'
};
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var mongoose = require('mongoose');
var User = mongoose.model('User');
passport.use(new LocalStrategy({
usernameField: 'user[email]',
passwordField: 'user[password]'
}, function(email, password, done) {
User.findOne({email: email}).then(function(user){
if(!user || !user.validPassword(password)){
return done(null, false, {errors: {'email or password': 'is invalid'}});
}
return done(null, user);
}).catch(done);
}));
var mongoose = require('mongoose');
var uniqueValidator = require('mongoose-unique-validator');
var slug = require('slug');
var User = mongoose.model('User');
var ArticleSchema = new mongoose.Schema({
slug: {type: String, lowercase: true, unique: true},
title: String,
description: String,
body: String,
favoritesCount: {type: Number, default: 0},
comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }],
tagList: [{ type: String }],
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
}, {timestamps: true, usePushEach: true});
ArticleSchema.plugin(uniqueValidator, {message: 'is already taken'});
ArticleSchema.pre('validate', function(next){
if(!this.slug) {
this.slugify();
}
next();
});
ArticleSchema.methods.slugify = function() {
this.slug = slug(this.title) + '-' + (Math.random() * Math.pow(36, 6) | 0).toString(36);
};
ArticleSchema.methods.updateFavoriteCount = function() {
var article = this;
return User.count({favorites: {$in: [article._id]}}).then(function(count){
article.favoritesCount = count;
return article.save();
});
};
ArticleSchema.methods.toJSONFor = function(user){
return {
slug: this.slug,
title: this.title,
description: this.description,
body: this.body,
createdAt: this.createdAt,
updatedAt: this.updatedAt,
tagList: this.tagList,
favorited: user ? user.isFavorite(this._id) : false,
favoritesCount: this.favoritesCount,
author: this.author.toProfileJSONFor(user)
};
};
mongoose.model('Article', ArticleSchema);
var mongoose = require('mongoose');
var CommentSchema = new mongoose.Schema({
body: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
article: { type: mongoose.Schema.Types.ObjectId, ref: 'Article' }
}, {timestamps: true, usePushEach: true});
// Requires population of author
CommentSchema.methods.toJSONFor = function(user){
return {
id: this._id,
body: this.body,
createdAt: this.createdAt,
author: this.author.toProfileJSONFor(user)
};
};
mongoose.model('Comment', CommentSchema);
var mongoose = require('mongoose');
var uniqueValidator = require('mongoose-unique-validator');
var crypto = require('crypto');
var jwt = require('jsonwebtoken');
var secret = require('../config').secret;
var UserSchema = new mongoose.Schema({
username: {type: String, unique: true, uniqueCaseInsensitive: true, required: [true, "can't be blank"], match: [/^[a-zA-Z0-9]+$/, 'is invalid'], index: true},
email: {type: String, unique: true, uniqueCaseInsensitive: true, required: [true, "can't be blank"], match: [/\S+@\S+\.\S+/, 'is invalid'], index: true},
bio: String,
image: String,
favorites: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Article' }],
following: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
hash: String,
salt: String
}, {timestamps: true, usePushEach: true});
UserSchema.plugin(uniqueValidator, {message: 'is already taken.'});
UserSchema.methods.validPassword = function(password) {
var hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex');
return this.hash === hash;
};
UserSchema.methods.setPassword = function(password){
this.salt = crypto.randomBytes(16).toString('hex');
this.hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex');
};
UserSchema.methods.generateJWT = function() {
var today = new Date();
var exp = new Date(today);
exp.setDate(today.getDate() + 60);
return jwt.sign({
id: this._id,
username: this.username,
exp: parseInt(exp.getTime() / 1000),
}, secret);
};
UserSchema.methods.toAuthJSON = function(){
return {
username: this.username,
email: this.email,
token: this.generateJWT(),
bio: this.bio,
image: this.image || 'https://static.productionready.io/images/smiley-cyrus.jpg'
};
};
UserSchema.methods.toProfileJSONFor = function(user){
return {
username: this.username,
bio: this.bio,
image: this.image || 'https://static.productionready.io/images/smiley-cyrus.jpg',
following: user ? user.isFollowing(this._id) : false
};
};
UserSchema.methods.favorite = function(id){
if(this.favorites.indexOf(id) === -1){
this.favorites.push(id);
}
return this.save();
};
UserSchema.methods.unfavorite = function(id){
this.favorites.remove(id);
return this.save();
};
UserSchema.methods.isFavorite = function(id){
return this.favorites.some(function(favoriteId){
return favoriteId.toString() === id.toString();
});
};
UserSchema.methods.follow = function(id){
if(this.following.indexOf(id) === -1){
this.following.push(id);
}
return this.save();
};
UserSchema.methods.unfollow = function(id){
this.following.remove(id);
return this.save();
};
UserSchema.methods.isFollowing = function(id){
return this.following.some(function(followId){
return followId.toString() === id.toString();
});
};
mongoose.model('User', UserSchema);
This diff is collapsed.
{
"name": "conduit-node",
"version": "2.0.0",
"description": "conduit on node",
"main": "app.js",
"scripts": {
"mongo:start": "docker run --name realworld-mongo -p 27017:27017 mongo & sleep 5",
"start": "node ./app.js",
"dev": "nodemon ./app.js",
"test": "newman run ./tests/api-tests.postman.json -e ./tests/env-api-tests.postman.json",
"stop": "lsof -ti :3000 | xargs kill",
"mongo:stop": "docker stop realworld-mongo && docker rm realworld-mongo"
},
"repository": {
"type": "git",
"url": "git+https://github.com/gothinkster/productionready-node-api.git"
},
"license": "ISC",
"dependencies": {
"body-parser": "1.15.0",
"cors": "2.7.1",
"ejs": "2.4.1",
"errorhandler": "1.4.3",
"express": "4.13.4",
"express-jwt": "3.3.0",
"express-session": "1.13.0",
"jsonwebtoken": "7.1.9",
"method-override": "2.3.5",
"methods": "1.1.2",
"mongoose": "^4.6.4",
"mongoose-unique-validator": "1.0.2",
"morgan": "1.7.0",
"passport": "0.3.2",
"passport-local": "1.0.0",
"request": "2.69.0",
"slug": "0.9.1",
"underscore": "1.8.3"
},
"devDependencies": {
"newman": "^3.8.2",
"nodemon": "^1.11.0"
}
}
te-backend/project-logo.png

51.9 KiB

var router = require('express').Router();
var mongoose = require('mongoose');
var Article = mongoose.model('Article');
var Comment = mongoose.model('Comment');
var User = mongoose.model('User');
var auth = require('../auth');
// Preload article objects on routes with ':article'
router.param('article', function(req, res, next, slug) {
Article.findOne({ slug: slug})
.populate('author')
.then(function (article) {
if (!article) { return res.sendStatus(404); }
req.article = article;
return next();
}).catch(next);
});
router.param('comment', function(req, res, next, id) {
Comment.findById(id).then(function(comment){
if(!comment) { return res.sendStatus(404); }
req.comment = comment;
return next();
}).catch(next);
});
router.get('/', auth.optional, function(req, res, next) {
var query = {};
var limit = 20;
var offset = 0;
if(typeof req.query.limit !== 'undefined'){
limit = req.query.limit;
}
if(typeof req.query.offset !== 'undefined'){
offset = req.query.offset;
}
if( typeof req.query.tag !== 'undefined' ){
query.tagList = {"$in" : [req.query.tag]};
}
Promise.all([
req.query.author ? User.findOne({username: req.query.author}) : null,
req.query.favorited ? User.findOne({username: req.query.favorited}) : null
]).then(function(results){
var author = results[0];
var favoriter = results[1];
if(author){
query.author = author._id;
}
if(favoriter){
query._id = {$in: favoriter.favorites};
} else if(req.query.favorited){
query._id = {$in: []};
}
return Promise.all([
Article.find(query)
.limit(Number(limit))
.skip(Number(offset))
.sort({createdAt: 'desc'})
.populate('author')
.exec(),
Article.count(query).exec(),
req.payload ? User.findById(req.payload.id) : null,
]).then(function(results){
var articles = results[0];
var articlesCount = results[1];
var user = results[2];
return res.json({
articles: articles.map(function(article){
return article.toJSONFor(user);
}),
articlesCount: articlesCount
});
});
}).catch(next);
});
router.get('/feed', auth.required, function(req, res, next) {
var limit = 20;
var offset = 0;
if(typeof req.query.limit !== 'undefined'){
limit = req.query.limit;
}
if(typeof req.query.offset !== 'undefined'){
offset = req.query.offset;
}
User.findById(req.payload.id).then(function(user){
if (!user) { return res.sendStatus(401); }
Promise.all([
Article.find({ author: {$in: user.following}})
.limit(Number(limit))
.sort({createdAt: 'desc'})
.skip(Number(offset))
.populate('author')
.exec(),
Article.count({ author: {$in: user.following}})
]).then(function(results){
var articles = results[0];
var articlesCount = results[1];
return res.json({
articles: articles.map(function(article){
return article.toJSONFor(user);
}),
articlesCount: articlesCount
});
}).catch(next);
});
});
router.post('/', auth.required, function(req, res, next) {
User.findById(req.payload.id).then(function(user){
if (!user) { return res.sendStatus(401); }
var article = new Article(req.body.article);
article.author = user;
return article.save().then(function(){
console.log(article.author);
return res.json({article: article.toJSONFor(user)});
});
}).catch(next);
});
// return a article
router.get('/:article', auth.optional, function(req, res, next) {
Promise.all([
req.payload ? User.findById(req.payload.id) : null,
req.article.populate('author').execPopulate()
]).then(function(results){
var user = results[0];
return res.json({article: req.article.toJSONFor(user)});
}).catch(next);
});
// update article
router.put('/:article', auth.required, function(req, res, next) {
User.findById(req.payload.id).then(function(user){
if(req.article.author._id.toString() === req.payload.id.toString()){
if(typeof req.body.article.title !== 'undefined'){
req.article.title = req.body.article.title;
}
if(typeof req.body.article.description !== 'undefined'){
req.article.description = req.body.article.description;
}
if(typeof req.body.article.body !== 'undefined'){
req.article.body = req.body.article.body;
}
if(typeof req.body.article.tagList !== 'undefined'){
req.article.tagList = req.body.article.tagList
}
req.article.save().then(function(article){
return res.json({article: article.toJSONFor(user)});
}).catch(next);
} else {
return res.sendStatus(403);
}
});
});
// delete article
router.delete('/:article', auth.required, function(req, res, next) {
User.findById(req.payload.id).then(function(user){
if (!user) { return res.sendStatus(401); }
if(req.article.author._id.toString() === req.payload.id.toString()){
return req.article.remove().then(function(){
return res.sendStatus(204);
});
} else {
return res.sendStatus(403);
}
}).catch(next);
});
// Favorite an article
router.post('/:article/favorite', auth.required, function(req, res, next) {
var articleId = req.article._id;
User.findById(req.payload.id).then(function(user){
if (!user) { return res.sendStatus(401); }
return user.favorite(articleId).then(function(){
return req.article.updateFavoriteCount().then(function(article){
return res.json({article: article.toJSONFor(user)});
});
});
}).catch(next);
});
// Unfavorite an article
router.delete('/:article/favorite', auth.required, function(req, res, next) {
var articleId = req.article._id;
User.findById(req.payload.id).then(function (user){
if (!user) { return res.sendStatus(401); }
return user.unfavorite(articleId).then(function(){
return req.article.updateFavoriteCount().then(function(article){
return res.json({article: article.toJSONFor(user)});
});
});
}).catch(next);
});
// return an article's comments
router.get('/:article/comments', auth.optional, function(req, res, next){
Promise.resolve(req.payload ? User.findById(req.payload.id) : null).then(function(user){
return req.article.populate({
path: 'comments',
populate: {
path: 'author'
},
options: {
sort: {
createdAt: 'desc'
}
}
}).execPopulate().then(function(article) {
return res.json({comments: req.article.comments.map(function(comment){
return comment.toJSONFor(user);
})});
});
}).catch(next);
});
// create a new comment
router.post('/:article/comments', auth.required, function(req, res, next) {
User.findById(req.payload.id).then(function(user){
if(!user){ return res.sendStatus(401); }
var comment = new Comment(req.body.comment);
comment.article = req.article;
comment.author = user;
return comment.save().then(function(){
req.article.comments.push(comment);
return req.article.save().then(function(article) {
res.json({comment: comment.toJSONFor(user)});
});
});
}).catch(next);
});
router.delete('/:article/comments/:comment', auth.required, function(req, res, next) {
if(req.comment.author.toString() === req.payload.id.toString()){
req.article.comments.remove(req.comment._id);
req.article.save()
.then(Comment.find({_id: req.comment._id}).remove().exec())
.then(function(){
res.sendStatus(204);
});
} else {
res.sendStatus(403);
}
});
module.exports = router;
var router = require('express').Router();
router.use('/', require('./users'));
router.use('/profiles', require('./profiles'));
router.use('/articles', require('./articles'));
router.use('/tags', require('./tags'));
router.use(function(err, req, res, next){
if(err.name === 'ValidationError'){
return res.status(422).json({
errors: Object.keys(err.errors).reduce(function(errors, key){
errors[key] = err.errors[key].message;
return errors;
}, {})
});
}
return next(err);
});
module.exports = router;
\ No newline at end of file
var router = require('express').Router();
var mongoose = require('mongoose');
var User = mongoose.model('User');
var auth = require('../auth');
// Preload user profile on routes with ':username'
router.param('username', function(req, res, next, username){
User.findOne({username: username}).then(function(user){
if (!user) { return res.sendStatus(404); }
req.profile = user;
return next();
}).catch(next);
});
router.get('/:username', auth.optional, function(req, res, next){
if(req.payload){
User.findById(req.payload.id).then(function(user){
if(!user){ return res.json({profile: req.profile.toProfileJSONFor(false)}); }
return res.json({profile: req.profile.toProfileJSONFor(user)});
});
} else {
return res.json({profile: req.profile.toProfileJSONFor(false)});
}
});
router.post('/:username/follow', auth.required, function(req, res, next){
var profileId = req.profile._id;
User.findById(req.payload.id).then(function(user){
if (!user) { return res.sendStatus(401); }
return user.follow(profileId).then(function(){
return res.json({profile: req.profile.toProfileJSONFor(user)});
});
}).catch(next);
});
router.delete('/:username/follow', auth.required, function(req, res, next){
var profileId = req.profile._id;
User.findById(req.payload.id).then(function(user){
if (!user) { return res.sendStatus(401); }
return user.unfollow(profileId).then(function(){
return res.json({profile: req.profile.toProfileJSONFor(user)});
});
}).catch(next);
});
module.exports = router;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment