Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Addison committed Nov 19, 2021
0 parents commit c7ff3cb
Show file tree
Hide file tree
Showing 55 changed files with 6,073 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
.env
188 changes: 188 additions & 0 deletions app.js
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}`);
})
22 changes: 22 additions & 0 deletions cloudinary/index.js
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
}
2 changes: 2 additions & 0 deletions config.js
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' });
83 changes: 83 additions & 0 deletions controllers/petshops.js
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 });
}
31 changes: 31 additions & 0 deletions controllers/reviews.js
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}`);
}
Loading

0 comments on commit c7ff3cb

Please sign in to comment.