Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
8a3a971
installed dotenv
valeeriiiee Sep 23, 2024
81fd8e8
installed mongoose
valeeriiiee Sep 23, 2024
9902643
refactor using ESM
valeeriiiee Sep 23, 2024
31f5cc4
Update package.json
valeeriiiee Sep 23, 2024
987e68b
Update server.js
valeeriiiee Sep 23, 2024
4006531
refactor code using ESM
valeeriiiee Sep 23, 2024
44d650b
refactor code using ESM
valeeriiiee Sep 23, 2024
c71a5d4
Update app.js
valeeriiiee Sep 23, 2024
e15faf2
Update server.js
valeeriiiee Sep 23, 2024
8bb7765
Create httpError.js
valeeriiiee Sep 23, 2024
6ba6427
Delete contacts.js
valeeriiiee Sep 23, 2024
9f103bf
Delete contacts.json
valeeriiiee Sep 23, 2024
5ea2d65
Create contactsModel.js
valeeriiiee Sep 23, 2024
04050ba
renamed contacts.js to contactsRouter.js
valeeriiiee Sep 23, 2024
47d0c65
Update app.js
valeeriiiee Sep 23, 2024
a36da85
Update contactsRouter.js
valeeriiiee Sep 23, 2024
00dc02e
Update contactsModel.js
valeeriiiee Sep 23, 2024
019021b
Create contactsController.js
valeeriiiee Sep 24, 2024
4d8dac4
Create ctrlWrapper.js
valeeriiiee Sep 24, 2024
26d083f
Create validation.js
valeeriiiee Sep 24, 2024
8052bdc
installed joi
valeeriiiee Sep 24, 2024
ea10067
Update contactsRouter.js
valeeriiiee Sep 24, 2024
0e22633
Update server.js
valeeriiiee Sep 24, 2024
14295fd
Update app.js
valeeriiiee Sep 24, 2024
864e79e
added images of testing
valeeriiiee Sep 24, 2024
a0f5ad6
delete images
valeeriiiee Sep 29, 2024
203c594
installed packages
valeeriiiee Sep 29, 2024
111106e
Update app.js
valeeriiiee Sep 29, 2024
80e8015
Create usersRouter.js
valeeriiiee Sep 29, 2024
ca72ca4
Create usersModel.js
valeeriiiee Sep 29, 2024
0de9d21
Create authenticateToken.js
valeeriiiee Sep 29, 2024
53e5a01
Update validation.js
valeeriiiee Sep 29, 2024
21654b3
Create usersController.js
valeeriiiee Sep 29, 2024
bff0015
added test images
valeeriiiee Sep 29, 2024
66ddda7
add authentication to contacts router
valeeriiiee Sep 29, 2024
b788ac5
Update contactsController.js
valeeriiiee Sep 29, 2024
0d31fb8
pagination and favorites filter
valeeriiiee Sep 29, 2024
51a4f6c
added test images for pagination and favorites filter
valeeriiiee Sep 29, 2024
50ad5ae
delete images
valeeriiiee Sep 29, 2024
c793f55
added avatar image
valeeriiiee Sep 29, 2024
444d2eb
Create loginUser.test.js
valeeriiiee Sep 29, 2024
b6bff20
Update server.js
valeeriiiee Sep 29, 2024
5b91696
Update contactsController.js
valeeriiiee Sep 29, 2024
c8eb015
Update usersController.js
valeeriiiee Sep 29, 2024
a5f0e39
Create upload.js
valeeriiiee Sep 29, 2024
55b2dd1
Update contactsModel.js
valeeriiiee Sep 29, 2024
2a9b73f
Update usersModel.js
valeeriiiee Sep 29, 2024
bd03a42
Update package-lock.json
valeeriiiee Sep 29, 2024
6d6cc68
Update package.json
valeeriiiee Sep 29, 2024
b75bd3b
Update contactsRouter.js
valeeriiiee Sep 29, 2024
dfd8db5
Update usersRouter.js
valeeriiiee Sep 29, 2024
325a12f
Update app.js
valeeriiiee Sep 29, 2024
ce2408d
Create .gitkeep
valeeriiiee Sep 30, 2024
423c319
Update contactsRouter.js
valeeriiiee Sep 30, 2024
f9568e8
Create jasper.jpg
valeeriiiee Sep 30, 2024
ab02b4c
Delete 0d1c02ee48d48f5797cb54f15d1d6c34.jpg
valeeriiiee Sep 30, 2024
3f63f4c
Create .gitkeep
valeeriiiee Sep 30, 2024
f5d98e6
Update usersController.js
valeeriiiee Sep 30, 2024
43415ea
Update package.json
valeeriiiee Sep 30, 2024
7a79514
Update loginUser.test.js
valeeriiiee Sep 30, 2024
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
40 changes: 23 additions & 17 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
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())
// tells Express to serve static files from the public directory)
// open http://localhost:3000/avatars/665c98dca10f7f28dc9eb8b2.jpeg on browser
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 * as 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 };
19 changes: 0 additions & 19 deletions models/contacts.js

This file was deleted.

Loading