Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

save #63

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

save #63

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
PORT=
DB_HOST=
SECRET_KEY=
13 changes: 13 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = {
env: {
commonjs: true,
es2021: true,
node: true,
jest: true,
},
extends: ["standard", "prettier"],
parserOptions: {
ecmaVersion: 12,
},
rules: {},
};
38 changes: 21 additions & 17 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
const express = require('express')
const logger = require('morgan')
const cors = require('cors')
import express from "express";
import logger from "morgan";
import cors from "cors";
import { router as contactsRouter } from "./routes/api/contactsRouter.js";
import { router as usersRouter } from "./routes/api/usersRouter.js";

const contactsRouter = require('./routes/api/contacts')
const app = express();

const app = express()
const formatsLogger = app.get("env") === "development" ? "dev" : "short";

const formatsLogger = app.get('env') === 'development' ? 'dev' : 'short'
app.use(logger(formatsLogger));
app.use(cors());
app.use(express.json());

app.use(logger(formatsLogger))
app.use(cors())
app.use(express.json())
app.use(express.static("public"));

app.use('/api/contacts', contactsRouter)
app.use("/api/contacts", contactsRouter);
app.use("/api/users", usersRouter);

app.use((req, res) => {
res.status(404).json({ message: 'Not found' })
})
app.use((_req, res) => {
res.status(404).json({ message: "Not found" });
});

app.use((err, req, res, next) => {
res.status(500).json({ message: err.message })
})
app.use((err, _req, res, _next) => {
const { status = 500, message = "Server error" } = err;
res.status(status).json({ message });
});

module.exports = app
export { app };
93 changes: 93 additions & 0 deletions controllers/contactsController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Contact } from "../models/contactsModel.js";
// prettier-ignore
import { contactValidation, favoriteValidation } from "../validations/validation.js";
import { httpError } from "../helpers/httpError.js";

const getAllContacts = async (req, res) => {
const { page = 1, limit = 20, favorite } = req.query;
const query = favorite ? { favorite: true } : {};

const result = await Contact.find(query)
.skip((page - 1) * limit)
.limit(parseInt(limit));

res.json(result);
};

const getContactById = async (req, res) => {
const { contactId } = req.params;
const result = await Contact.findById(contactId);

if (!result) {
throw httpError(404, "Contact ID Not Found");
}

res.json(result);
};

const addContact = async (req, res) => {
// Preventing lack of necessary data for contacts (check validations folder)
const { error } = contactValidation.validate(req.body);

if (error) {
throw httpError(400, "missing required fields");
}

const result = await Contact.create(req.body);

res.status(201).json(result);
};

const deleteContactById = async (req, res) => {
const { contactId } = req.params;
const result = await Contact.findByIdAndDelete(contactId);

if (!result) {
throw httpError(404);
}

res.json({
message: "Contact deleted",
});
};

const updateContactById = async (req, res) => {
// Preventing lack of necessary data for contacts (check validations folder)
const { error } = contactValidation.validate(req.body);
if (error) {
throw httpError(400, "missing fields");
}

const { contactId } = req.params;
const result = await Contact.findByIdAndUpdate(contactId, req.body, {
new: true,
});

if (!result) {
throw httpError(404);
}

res.json(result);
};

const updateStatusContact = async (req, res) => {
// Preventing lack of necessary data for favorite (check validations folder)
const { error } = favoriteValidation.validate(req.body);
if (error) {
throw httpError(400, "missing field favorite");
}

const { contactId } = req.params;
const result = await Contact.findByIdAndUpdate(contactId, req.body, {
new: true,
});

if (!result) {
throw httpError(404);
}

res.json(result);
};

// prettier-ignore
export { getAllContacts, getContactById, addContact, deleteContactById, updateContactById, updateStatusContact};
147 changes: 147 additions & 0 deletions controllers/usersController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import bcrypt from "bcrypt";
import gravatar from "gravatar";
import jwt from "jsonwebtoken";
import "dotenv/config";
import Jimp from "jimp";
import path from "path";
import fs from "fs/promises";
import { User } from "../models/usersModel.js";
// prettier-ignore
import { signupValidation, subscriptionValidation } from "../validations/validation.js";
import { httpError } from "../helpers/httpError.js";

const { SECRET_KEY } = process.env;

const signupUser = async (req, res) => {
const { email, password } = req.body;

// Registration validation error
const { error } = signupValidation.validate(req.body);
if (error) {
throw httpError(400, error.message);
}

// Registration conflict error
const user = await User.findOne({ email });
if (user) {
throw httpError(409, "Email in Use");
}

const hashPassword = await bcrypt.hash(password, 10);

// Create a link to the user's avatar with gravatar
const avatarURL = gravatar.url(email, { protocol: "http" });

const newUser = await User.create({
email,
password: hashPassword,
avatarURL,
});

// Registration success response
res.status(201).json({
user: {
email: newUser.email,
subscription: newUser.subscription,
avatarURL: newUser.avatarURL,
},
});
};

const loginUser = async (req, res) => {
const { email, password } = req.body;

// Login validation error
const { error } = signupValidation.validate(req.body);
if (error) {
throw httpError(401, error.message);
}

// Login auth error (email)
const user = await User.findOne({ email });
if (!user) {
throw httpError(401, "Email or password is wrong");
}

// Login auth error (password)
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
throw httpError(401, "Email or password is wrong");
}

const payload = { id: user._id };
const token = jwt.sign(payload, SECRET_KEY, { expiresIn: "23h" });

await User.findByIdAndUpdate(user._id, { token });

// Login success response
res.status(200).json({
token: token,
user: {
email: user.email,
subscription: user.subscription,
},
});
};

const logoutUser = async (req, res) => {
const { _id } = req.user;

// Logout unauthorized error (setting token to empty string will remove token -> will logout)
await User.findByIdAndUpdate(_id, { token: "" });

// Logout success response
res.status(204).send();
};

const getCurrentUsers = async (req, res) => {
const { email, subscription } = req.user;

res.json({
email,
subscription,
});
};

const updateUserSubscription = async (req, res) => {
const { error } = subscriptionValidation.validate(req.body);
if (error) {
throw httpError(400, error.message);
}

const { _id } = req.user;

const updatedUser = await User.findByIdAndUpdate(_id, req.body, {
new: true,
});

res.json({
email: updatedUser.email,
subscription: updatedUser.subscription,
});
};

const updateAvatar = async (req, res) => {
const { _id } = req.user;
const { path: oldPath, originalname } = req.file;

await Jimp.read(oldPath).then((image) =>
// image.resize(250, 250).write(oldPath)
image.cover(250, 250).write(oldPath)
);

const extension = path.extname(originalname);
const filename = `${_id}${extension}`;

const newPath = path.join("public", "avatars", filename);
await fs.rename(oldPath, newPath);

let avatarURL = path.join("/avatars", filename);
avatarURL = avatarURL.replace(/\\/g, "/");

await User.findByIdAndUpdate(_id, { avatarURL });
res.status(200).json({ avatarURL });
};

// prettier-ignore
export { signupUser, loginUser, logoutUser, getCurrentUsers, updateUserSubscription, updateAvatar};
12 changes: 12 additions & 0 deletions helpers/ctrlWrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const ctrlWrapper = (ctrl) => {
const func = async (req, res, next) => {
try {
await ctrl(req, res, next);
} catch (error) {
next(error);
}
};
return func;
};

export { ctrlWrapper };
15 changes: 15 additions & 0 deletions helpers/httpError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const messages = {
400: "Bad request",
401: "Unauthorized",
403: "Forbidden",
404: "Not found",
409: "Conflict",
};

const httpError = (status, message = messages[status]) => {
const error = new Error(message);
error.status = status;
return error;
};

export { httpError };
30 changes: 30 additions & 0 deletions middlewares/authenticateToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import jwt from "jsonwebtoken";
import { User } from "../models/usersModel.js";
import { httpError } from "../helpers/httpError.js";
import "dotenv/config";
const { SECRET_KEY } = process.env;

const authenticateToken = async (req, _res, next) => {
const { authorization = "" } = req.headers;
const [bearer, token] = authorization.split(" ");

if (bearer !== "Bearer") {
next(httpError(401, "Not authorized"));
}

try {
const { id } = jwt.verify(token, SECRET_KEY);
const user = await User.findById(id);

if (!user || user.token !== token || !user.token) {
next(httpError(401, "Not authorized"));
}

req.user = user;
next();
} catch {
next(httpError(401, "Not authorized"));
}
};

export { authenticateToken };
17 changes: 17 additions & 0 deletions middlewares/upload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import multer from "multer";
import path from "path";

const tempPath = path.join("tmp");

const multerConfig = multer.diskStorage({
destination: tempPath,
filename: (req, file, cb) => {
cb(null, file.originalname);
},
});

const upload = multer({
storage: multerConfig,
});

export { upload };
Loading