-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Addison
committed
Nov 19, 2021
0 parents
commit c7ff3cb
Showing
55 changed files
with
6,073 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
if (process.env.NODE_ENV !== "production") { | ||
require('dotenv').config(); | ||
} | ||
|
||
const express = require('express'); | ||
const path = require('path'); | ||
const mongoose = require('mongoose'); | ||
const ejsMate = require('ejs-mate'); | ||
const session = require('express-session'); | ||
const flash = require('connect-flash'); | ||
const ExpressError = require('./utils/ExpressError'); | ||
const methodOverride = require('method-override'); | ||
const passport = require('passport'); | ||
const LocalStrategy = require('passport-local'); | ||
const moment = require('moment'); | ||
const mongoSanitize = require('express-mongo-sanitize'); | ||
const helmet = require('helmet'); | ||
const MongoStore = require('connect-mongo'); | ||
|
||
const User = require('./models/user'); | ||
const petshopRoutes = require('./routes/petshops'); | ||
const reviewRoutes = require('./routes/reviews') | ||
const userRoutes = require('./routes/users'); | ||
const PetShop = require('./models/petshop'); | ||
|
||
const dbUrl = process.env.DB_URL || 'mongodb://localhost:27017/petFrontier'; | ||
const secret = process.end.SECRET || 'petfrontier' | ||
|
||
// mongoose config | ||
main().catch(err => console.log(err)); | ||
|
||
async function main() { | ||
await mongoose.connect(dbUrl); | ||
console.log('Mongodb connection open.'); | ||
} | ||
|
||
const app = express(); | ||
const port = 3000; | ||
|
||
|
||
// *********************** config *********************** | ||
// set up ejs view engine, views directory, and ejs-mate | ||
app.engine('ejs', ejsMate); | ||
app.set('view engine', 'ejs'); | ||
app.set('views', path.join(__dirname, 'views')); | ||
|
||
// parse request body | ||
app.use(express.urlencoded({ extended: true })); | ||
// send patch and put requests | ||
app.use(methodOverride('_method')); | ||
// serve public directory | ||
app.use(express.static(path.join(__dirname, 'public'))); | ||
// prevent mongo operator injection | ||
app.use(mongoSanitize()); | ||
|
||
// create local variable available for the application | ||
moment.locale('zh-cn'); | ||
app.locals.moment = moment; | ||
|
||
// session setup | ||
const store = MongoStore.create({ | ||
mongoUrl: dbUrl, | ||
secret: secret, | ||
touchAfter: 24 * 3600 | ||
}) | ||
|
||
store.on('error', function (e) { | ||
console.log('SESSION STORE ERROR', e) | ||
}) | ||
|
||
const sessionConfig = { | ||
store, | ||
name: 'sessionPF', | ||
secret: secret, | ||
resave: false, | ||
saveUninitialized: true, | ||
cookie: { | ||
httpOnly: true, | ||
// secure: true, | ||
// one-week-expiration | ||
expires: Date.now() + 1000 * 60 * 60 * 24 * 7, | ||
maxAge: 1000 * 60 * 60 * 24 * 7, | ||
} | ||
} | ||
app.use(session(sessionConfig)); | ||
|
||
// connect-flash | ||
app.use(flash()); | ||
|
||
// helmet security headers setup | ||
app.use(helmet()); | ||
const scriptSrcUrls = [ | ||
"https://cdn.jsdelivr.net/", | ||
"https://code.jquery.com/", | ||
"https://webapi.amap.com/", | ||
"https://vdata.amap.com/", | ||
"https://restapi.amap.com/", | ||
"https://cache.amap.com/", | ||
"https://kit.fontawesome.com/", | ||
"https://cdnjs.cloudflare.com/", | ||
"https://cdn.jsdelivr.net", | ||
]; | ||
const styleSrcUrls = [ | ||
"https://kit.fontawesome.com/", | ||
"https://cdn.jsdelivr.net/", | ||
"https://vdata.amap.com/", | ||
"https://fonts.googleapis.com/", | ||
"https://use.fontawesome.com/", | ||
]; | ||
const connectSrcUrls = [ | ||
"https://ka-f.fontawesome.com/", | ||
"https://vdata.amap.com/", | ||
]; | ||
const fontSrcUrls = [ | ||
"https://fonts.gstatic.com/", | ||
"https://ka-f.fontawesome.com/", | ||
]; | ||
app.use( | ||
helmet.contentSecurityPolicy({ | ||
directives: { | ||
defaultSrc: [], | ||
connectSrc: ["'self'", ...connectSrcUrls], | ||
scriptSrc: ["'unsafe-inline'", "'self'","'unsafe-eval'", ...scriptSrcUrls], | ||
styleSrc: ["'self'", "'unsafe-inline'", ...styleSrcUrls], | ||
workerSrc: ["'self'", "blob:"], | ||
objectSrc: [], | ||
imgSrc: [ | ||
"'self'", | ||
"blob:", | ||
"data:", | ||
"https://res.cloudinary.com/addisonshang/", | ||
"https://images.unsplash.com/", | ||
"https://webapi.amap.com/", | ||
"https://vdata.amap.com/", | ||
], | ||
fontSrc: ["'self'", ...fontSrcUrls], | ||
}, | ||
}) | ||
); | ||
|
||
|
||
// passport authentication | ||
app.use(passport.initialize()); | ||
app.use(passport.session()); | ||
// use static authenticate method of model in LocalStrategy | ||
passport.use(new LocalStrategy(User.authenticate())); | ||
// use static serialize and deserialize of model for passport session support | ||
passport.serializeUser(User.serializeUser()); | ||
passport.deserializeUser(User.deserializeUser()); | ||
|
||
|
||
// middlewares: flash & user authentication | ||
app.use((req, res, next) => { | ||
res.locals.currentUser = req.user; | ||
res.locals.success = req.flash('success'); | ||
res.locals.error = req.flash('error'); | ||
next(); | ||
}) | ||
|
||
// routes | ||
// homepage | ||
app.get('/', async(req, res) => { | ||
const allShops = await PetShop.find({}); | ||
res.render('home', { allShops }); | ||
}) | ||
|
||
// users | ||
app.use('/', userRoutes); | ||
// petshops | ||
app.use('/petshops', petshopRoutes); | ||
// reviews | ||
app.use('/petshops/:id/reviews/', reviewRoutes); | ||
|
||
|
||
// error handlers | ||
app.all('*', (req, res, next) => { | ||
next(new ExpressError('Page Not Found', 404)); | ||
}) | ||
|
||
app.use((err, req, res, next) => { | ||
const { statusCode = 500 } = err; | ||
if (!err.message) err.message = 'Oops, something went wrong' | ||
res.status(statusCode).render('error', { err }); | ||
}) | ||
|
||
app.listen(port, () => { | ||
console.log(`Serving on port ${port}`); | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
const cloudinary = require('cloudinary').v2; | ||
const { CloudinaryStorage } = require('multer-storage-cloudinary'); | ||
|
||
|
||
cloudinary.config({ | ||
cloud_name: process.env.CLOUDINARY_CLOUD_NAME, | ||
api_key: process.env.CLOUDINARY_KEY, | ||
api_secret: process.env.CLOUDINARY_SECRET, | ||
}); | ||
|
||
const storage = new CloudinaryStorage({ | ||
cloudinary, | ||
params: { | ||
folder: 'PetFrontier', | ||
allowedFormats: ['jpeg', 'png', 'jpg'], | ||
} | ||
}); | ||
|
||
module.exports = { | ||
cloudinary, | ||
storage | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import dotenv from "dotenv"; | ||
dotenv.config({ silent: process.env.NODE_ENV === 'production' }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
const PetShop = require('../models/petshop'); | ||
const { cloudinary } = require('../cloudinary'); | ||
|
||
|
||
module.exports.index = async (req, res) => { | ||
if (!req.query.page) { | ||
const allShops = await PetShop.paginate({}) // array with docs info | ||
const shopsData = await PetShop.find({}) | ||
res.render('petshops/index', { allShops, shopsData }) | ||
} else { | ||
const { page } = req.query; | ||
const allShops = await PetShop.paginate({}, { | ||
page | ||
}) | ||
const shopsData = await PetShop.find({}) | ||
res.render('petshops/index', { allShops, shopsData }) | ||
} | ||
|
||
} | ||
|
||
module.exports.renderNewForm = (req, res) => { | ||
res.render('petshops/new') | ||
} | ||
|
||
module.exports.createPetshop = async (req, res, next) => { | ||
const newShop = new PetShop(req.body.petshop); | ||
newShop.images = req.files.map(f => ({ url: f.path, filename: f.filename })); | ||
newShop.owner = { | ||
id: req.user._id, | ||
username: req.user.username | ||
} | ||
await newShop.save(); | ||
req.flash('success', '店铺创建成功'); | ||
res.redirect(`/petshops/${newShop._id}`) | ||
} | ||
|
||
module.exports.renderEditForm = async (req, res) => { | ||
const { id } = req.params; | ||
const selectedShop = await PetShop.findById(id); | ||
if (!selectedShop) { | ||
req.flash('error', '店铺不存在'); | ||
return res.redirect('/petshops'); | ||
} | ||
res.render('petshops/edit', { selectedShop }); | ||
} | ||
|
||
module.exports.editPetshop = async (req, res,) => { | ||
const { id } = req.params; | ||
const editShop = await PetShop.findByIdAndUpdate(id, { ...req.body.petshop }, { runValidators: true, new: true }) | ||
const imgs = req.files.map(f => ({ url: f.path, filename: f.filename })); | ||
editShop.images.push(...imgs); | ||
await editShop.save(); | ||
if (req.body.deleteImages) { | ||
for (let filename of req.body.deleteImages) { | ||
await cloudinary.uploader.destroy(filename); | ||
} | ||
await editShop.updateOne({ $pull: { images: { filename: { $in: req.body.deleteImages } } } }); | ||
} | ||
req.flash('success', '店铺更新成功'); | ||
res.redirect(`/petshops/${editShop._id}`) | ||
} | ||
|
||
module.exports.deletePetshop = async (req, res) => { | ||
const { id } = req.params; | ||
const deletedShop = await PetShop.findOneAndDelete({ _id: id }, { runValidators: true, new: true }) | ||
req.flash('success', '店铺删除成功'); | ||
res.redirect(`/petshops`) | ||
} | ||
|
||
module.exports.showPetshop = async (req, res) => { | ||
const { id } = req.params; | ||
const selectedShop = await PetShop.findById(id).populate({ | ||
path: 'reviews', | ||
populate: { | ||
path: 'author', | ||
} | ||
}).populate('owner'); | ||
if (!selectedShop) { | ||
req.flash('error', '店铺不存在'); | ||
return res.redirect('/petshops'); | ||
} | ||
res.render('petshops/show', { selectedShop }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
const PetShop = require('../models/petshop') | ||
const Review = require('../models/review') | ||
|
||
|
||
module.exports.createReview = async (req, res) => { | ||
const { id } = req.params; | ||
const selectedShop = await PetShop.findById(id); | ||
if (!req.isAuthenticated()) { | ||
req.session.returnTo = `/petshops/${selectedShop._id}`; | ||
req.flash('error', '请登录'); | ||
return res.redirect('/login'); | ||
} | ||
const newReview = new Review(req.body.review); | ||
newReview.author = { | ||
id: req.user._id, | ||
username: req.user.username | ||
} | ||
selectedShop.reviews.push(newReview) | ||
await newReview.save(); | ||
await selectedShop.save(); | ||
req.flash('success', '评价发布成功'); | ||
res.redirect(`/petshops/${selectedShop._id}`); | ||
} | ||
|
||
module.exports.deleteReview = async (req, res) => { | ||
const { id, reviewId } = req.params; | ||
await PetShop.findByIdAndUpdate(id, { $pull: { reviews: reviewId } }); | ||
await Review.findByIdAndDelete(reviewId); | ||
req.flash('success', '评价已删除'); | ||
res.redirect(`/petshops/${id}`); | ||
} |
Oops, something went wrong.