From 0c46c6f76585d352af5a99f24f0872f1719006d2 Mon Sep 17 00:00:00 2001 From: Kacpero60 Date: Mon, 10 Feb 2025 17:11:13 +0100 Subject: [PATCH 1/6] gg 4 --- app.js | 31 +++++++++++------------ controllers/contacts.js | 54 +++++++++++++++++++++++++++++++++++++++++ middlewares/auth.js | 29 ++++++++++++++++++++++ models/contact.js | 27 +++++++++++++++++++++ models/contacts.js | 29 ++++++++++++++++------ models/user.js | 26 ++++++++++++++++++++ routes/api/contacts.js | 38 +++++++++++++---------------- 7 files changed, 189 insertions(+), 45 deletions(-) create mode 100644 controllers/contacts.js create mode 100644 middlewares/auth.js create mode 100644 models/contact.js create mode 100644 models/user.js diff --git a/app.js b/app.js index 40fd9bc167f..461994c15fa 100644 --- a/app.js +++ b/app.js @@ -1,25 +1,22 @@ -const express = require('express') -const logger = require('morgan') -const cors = require('cors') +const express = require('express'); +const cors = require('cors'); +require('dotenv').config(); +const contactsRouter = require('./routes/api/contacts'); -const contactsRouter = require('./routes/api/contacts') +const app = express(); -const app = express() +app.use(cors()); +app.use(express.json()); -const formatsLogger = app.get('env') === 'development' ? 'dev' : 'short' - -app.use(logger(formatsLogger)) -app.use(cors()) -app.use(express.json()) - -app.use('/api/contacts', contactsRouter) +app.use('/api/contacts', contactsRouter); app.use((req, res) => { - res.status(404).json({ message: 'Not found' }) -}) + res.status(404).json({ message: 'Not found' }); +}); app.use((err, req, res, next) => { - res.status(500).json({ message: err.message }) -}) + res.status(500).json({ message: err.message }); +}); + +module.exports = app; -module.exports = app diff --git a/controllers/contacts.js b/controllers/contacts.js new file mode 100644 index 00000000000..5e2e93133bf --- /dev/null +++ b/controllers/contacts.js @@ -0,0 +1,54 @@ +const Contact = require('../models/contacts'); + +const getAllContacts = async (req, res) => { + const { _id: owner } = req.user; + const contacts = await Contact.listContacts(owner); + res.json(contacts); +}; + +const getContactById = async (req, res) => { + const { contactId } = req.params; + const { _id: owner } = req.user; + + const contact = await Contact.getContactById(contactId, owner); + if (!contact) { + return res.status(404).json({ message: 'Not found' }); + } + res.json(contact); +}; + +const addContact = async (req, res) => { + const { _id: owner } = req.user; + const newContact = await Contact.addContact(req.body, owner); + res.status(201).json(newContact); +}; + +const updateContact = async (req, res) => { + const { contactId } = req.params; + const { _id: owner } = req.user; + + const updatedContact = await Contact.updateContact(contactId, req.body, owner); + if (!updatedContact) { + return res.status(404).json({ message: 'Not found' }); + } + res.json(updatedContact); +}; + +const deleteContact = async (req, res) => { + const { contactId } = req.params; + const { _id: owner } = req.user; + + const deletedContact = await Contact.removeContact(contactId, owner); + if (!deletedContact) { + return res.status(404).json({ message: 'Not found' }); + } + res.json({ message: 'Contact deleted' }); +}; + +module.exports = { + getAllContacts, + getContactById, + addContact, + updateContact, + deleteContact, +}; diff --git a/middlewares/auth.js b/middlewares/auth.js new file mode 100644 index 00000000000..3dda839ce65 --- /dev/null +++ b/middlewares/auth.js @@ -0,0 +1,29 @@ +const jwt = require('jsonwebtoken'); +const User = require('../models/user'); + +const { SECRET_KEY } = process.env; + +const authenticate = async (req, res, next) => { + const { authorization = '' } = req.headers; + const [bearer, token] = authorization.split(' '); + + if (bearer !== 'Bearer' || !token) { + return res.status(401).json({ message: 'Not authorized' }); + } + + try { + const { id } = jwt.verify(token, SECRET_KEY); + const user = await User.findById(id); + + if (!user || user.token !== token) { + return res.status(401).json({ message: 'Not authorized' }); + } + + req.user = user; + next(); + } catch (error) { + return res.status(401).json({ message: 'Not authorized' }); + } +}; + +module.exports = authenticate; diff --git a/models/contact.js b/models/contact.js new file mode 100644 index 00000000000..393865c8546 --- /dev/null +++ b/models/contact.js @@ -0,0 +1,27 @@ +const { Schema, model } = require('mongoose'); + +const contactSchema = new Schema({ + name: { + type: String, + required: [true, 'Set name for contact'], + }, + email: { + type: String, + }, + phone: { + type: String, + }, + favorite: { + type: Boolean, + default: false, + }, + owner: { + type: Schema.Types.ObjectId, + ref: 'user', + required: true, + }, +}, { versionKey: false, timestamps: true }); + +const Contact = model('Contact', contactSchema); + +module.exports = Contact; diff --git a/models/contacts.js b/models/contacts.js index 409d11c7c09..5ce4dffa37f 100644 --- a/models/contacts.js +++ b/models/contacts.js @@ -1,14 +1,29 @@ -// const fs = require('fs/promises') +const Contact = require('./contact'); -const listContacts = async () => {} +// Lista wszystkich kontaktów użytkownika +const listContacts = async (owner) => { + return await Contact.find({ owner }); +}; -const getContactById = async (contactId) => {} +// Pobranie kontaktu po ID (dla konkretnego użytkownika) +const getContactById = async (contactId, owner) => { + return await Contact.findOne({ _id: contactId, owner }); +}; -const removeContact = async (contactId) => {} +// Dodanie nowego kontaktu +const addContact = async (body, owner) => { + return await Contact.create({ ...body, owner }); +}; -const addContact = async (body) => {} +// Aktualizacja kontaktu +const updateContact = async (contactId, body, owner) => { + return await Contact.findOneAndUpdate({ _id: contactId, owner }, body, { new: true }); +}; -const updateContact = async (contactId, body) => {} +// Usuwanie kontaktu +const removeContact = async (contactId, owner) => { + return await Contact.findOneAndDelete({ _id: contactId, owner }); +}; module.exports = { listContacts, @@ -16,4 +31,4 @@ module.exports = { removeContact, addContact, updateContact, -} +}; diff --git a/models/user.js b/models/user.js new file mode 100644 index 00000000000..56725a55fdb --- /dev/null +++ b/models/user.js @@ -0,0 +1,26 @@ +const { Schema, model } = require('mongoose'); + +const userSchema = new Schema({ + password: { + type: String, + required: [true, 'Password is required'], + }, + email: { + type: String, + required: [true, 'Email is required'], + unique: true, + }, + subscription: { + type: String, + enum: ['starter', 'pro', 'business'], + default: 'starter', + }, + token: { + type: String, + default: null, + }, +}, { versionKey: false, timestamps: true }); + +const User = model('user', userSchema); + +module.exports = User; diff --git a/routes/api/contacts.js b/routes/api/contacts.js index a60ebd69231..ac9b4e4a17c 100644 --- a/routes/api/contacts.js +++ b/routes/api/contacts.js @@ -1,25 +1,21 @@ -const express = require('express') +const express = require('express'); +const authenticate = require('../../middlewares/auth'); +const { + getAllContacts, + getContactById, + addContact, + updateContact, + deleteContact, +} = require('../../controllers/contacts'); -const router = express.Router() +const router = express.Router(); -router.get('/', async (req, res, next) => { - res.json({ message: 'template message' }) -}) +router.use(authenticate); // Ochrona tras -router.get('/:contactId', async (req, res, next) => { - res.json({ message: 'template message' }) -}) +router.get('/', getAllContacts); +router.get('/:contactId', getContactById); +router.post('/', addContact); +router.put('/:contactId', updateContact); +router.delete('/:contactId', deleteContact); -router.post('/', async (req, res, next) => { - res.json({ message: 'template message' }) -}) - -router.delete('/:contactId', async (req, res, next) => { - res.json({ message: 'template message' }) -}) - -router.put('/:contactId', async (req, res, next) => { - res.json({ message: 'template message' }) -}) - -module.exports = router +module.exports = router; From ad9e471d6c82597f25ebd09c5153befeae4ccaf7 Mon Sep 17 00:00:00 2001 From: Kacpero60 Date: Tue, 11 Feb 2025 13:36:22 +0100 Subject: [PATCH 2/6] gg --- controllers/users.js | 118 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 controllers/users.js diff --git a/controllers/users.js b/controllers/users.js new file mode 100644 index 00000000000..004c58795c0 --- /dev/null +++ b/controllers/users.js @@ -0,0 +1,118 @@ +const bcrypt = require('bcryptjs'); +const jwt = require('jsonwebtoken'); +const gravatar = require('gravatar'); +const fs = require('fs/promises'); +const path = require('path'); +const jimp = require('jimp'); +const User = require('../models/user'); + +const { SECRET_KEY } = process.env; +const avatarsDir = path.join(__dirname, '../public/avatars'); + +// ✅ Rejestracja użytkownika +const signup = async (req, res) => { + const { email, password } = req.body; + const user = await User.findOne({ email }); + + if (user) { + return res.status(409).json({ message: 'Email in use' }); + } + + const hashedPassword = await bcrypt.hash(password, 10); + const avatarURL = gravatar.url(email); + + const newUser = await User.create({ + email, + password: hashedPassword, + avatarURL, + }); + + res.status(201).json({ + user: { + email: newUser.email, + subscription: newUser.subscription, + }, + }); +}; + +// ✅ Logowanie użytkownika +const login = async (req, res) => { + const { email, password } = req.body; + const user = await User.findOne({ email }); + + if (!user || !(await bcrypt.compare(password, user.password))) { + return res.status(401).json({ message: 'Email or password is wrong' }); + } + + const token = jwt.sign({ id: user._id }, SECRET_KEY, { expiresIn: '1h' }); + user.token = token; + await user.save(); + + res.json({ + token, + user: { + email: user.email, + subscription: user.subscription, + }, + }); +}; + +// ✅ Wylogowanie użytkownika +const logout = async (req, res) => { + const { _id } = req.user; + await User.findByIdAndUpdate(_id, { token: null }); + res.status(204).send(); +}; + +// ✅ Bieżący użytkownik +const getCurrentUser = async (req, res) => { + const { email, subscription } = req.user; + res.json({ email, subscription }); +}; + +// ✅ Aktualizacja awatara +const updateAvatar = async (req, res) => { + try { + if (!req.file) { + return res.status(400).json({ message: 'No file uploaded.' }); + } + + const { path: tempUpload, originalname } = req.file; + const { _id } = req.user; + + const uniqueName = `${_id}-${Date.now()}${path.extname(originalname)}`; + const resultUpload = path.join(avatarsDir, uniqueName); + + const image = await jimp.read(tempUpload); + await image.resize(250, 250).writeAsync(resultUpload); + + await fs.unlink(tempUpload); + + const avatarURL = `/avatars/${uniqueName}`; + + const updatedUser = await User.findByIdAndUpdate( + _id, + { avatarURL }, + { new: true } + ); + + res.json({ + avatarURL: `${req.protocol}://${req.get('host')}${avatarURL}`, + user: { + email: updatedUser.email, + subscription: updatedUser.subscription, + }, + }); + } catch (error) { + res.status(500).json({ message: 'Błąd podczas aktualizacji awatara.' }); + } +}; + +// ✅ Eksportowanie funkcji +module.exports = { + signup, + login, + logout, + getCurrentUser, + updateAvatar, +}; From 892839f7ae65115ccdbd9eaf9c03541a33815b25 Mon Sep 17 00:00:00 2001 From: Kacpero60 Date: Tue, 11 Feb 2025 13:37:09 +0100 Subject: [PATCH 3/6] gg --- middlewares/auth.js | 1 + 1 file changed, 1 insertion(+) diff --git a/middlewares/auth.js b/middlewares/auth.js index 3dda839ce65..524407042c8 100644 --- a/middlewares/auth.js +++ b/middlewares/auth.js @@ -22,6 +22,7 @@ const authenticate = async (req, res, next) => { req.user = user; next(); } catch (error) { + console.error('Auth error:', error.message); return res.status(401).json({ message: 'Not authorized' }); } }; From 5e5a3ca428b5728d4ed258000bf491ff20643924 Mon Sep 17 00:00:00 2001 From: Kacpero60 Date: Tue, 11 Feb 2025 13:37:42 +0100 Subject: [PATCH 4/6] Create users.js --- routes/api/users.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 routes/api/users.js diff --git a/routes/api/users.js b/routes/api/users.js new file mode 100644 index 00000000000..65e6fd7b8c1 --- /dev/null +++ b/routes/api/users.js @@ -0,0 +1,21 @@ +const express = require('express'); +const authenticate = require('../../middlewares/auth'); +const upload = require('../../middlewares/upload'); + +const { + signup, + login, + logout, + getCurrentUser, + updateAvatar, +} = require('../../controllers/users'); + +const router = express.Router(); + +router.post('/signup', signup); +router.post('/login', login); +router.get('/logout', authenticate, logout); +router.get('/current', authenticate, getCurrentUser); +router.patch('/avatars', authenticate, upload.single('avatar'), updateAvatar); + +module.exports = router; From 63c1d993f53edeeefd2366bfa003369d34602f71 Mon Sep 17 00:00:00 2001 From: Kacpero60 Date: Tue, 11 Feb 2025 13:39:52 +0100 Subject: [PATCH 5/6] Create upload.js --- middlewares/upload.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 middlewares/upload.js diff --git a/middlewares/upload.js b/middlewares/upload.js new file mode 100644 index 00000000000..e16c41f7626 --- /dev/null +++ b/middlewares/upload.js @@ -0,0 +1,30 @@ +const multer = require('multer'); +const path = require('path'); + +// Folder tymczasowy do przechowywania przesłanych plików +const tempDir = path.join(__dirname, '../tmp'); + +// Konfiguracja Multer +const storage = multer.diskStorage({ + destination: (req, file, cb) => { + cb(null, tempDir); // Zapis w folderze tmp + }, + filename: (req, file, cb) => { + const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); + const extension = path.extname(file.originalname); + cb(null, `${file.fieldname}-${uniqueSuffix}${extension}`); + } +}); + +// Filtr plików - akceptujemy tylko obrazy +const fileFilter = (req, file, cb) => { + if (file.mimetype.startsWith('image/')) { + cb(null, true); + } else { + cb(new Error('Nieprawidłowy format pliku. Dozwolone są tylko obrazy.'), false); + } +}; + +const upload = multer({ storage, fileFilter }); + +module.exports = upload; From 17944f2e4b87dc4efd7e8a2833665449a6acbece Mon Sep 17 00:00:00 2001 From: Kacpero60 Date: Tue, 11 Feb 2025 13:48:35 +0100 Subject: [PATCH 6/6] gg 5 --- app.js | 7 ++++++- controllers/users.js | 23 ++++++++++------------- middlewares/auth.js | 2 +- middlewares/upload.js | 5 +---- models/user.js | 4 ++++ routes/api/users.js | 1 - server.js | 6 +++--- 7 files changed, 25 insertions(+), 23 deletions(-) diff --git a/app.js b/app.js index 461994c15fa..5c6a4e491ed 100644 --- a/app.js +++ b/app.js @@ -1,14 +1,20 @@ const express = require('express'); const cors = require('cors'); require('dotenv').config(); + const contactsRouter = require('./routes/api/contacts'); +const usersRouter = require('./routes/api/users'); const app = express(); app.use(cors()); app.use(express.json()); +// Ustawienie folderu public jako statycznego +app.use('/public', express.static('public')); + app.use('/api/contacts', contactsRouter); +app.use('/api/users', usersRouter); app.use((req, res) => { res.status(404).json({ message: 'Not found' }); @@ -19,4 +25,3 @@ app.use((err, req, res, next) => { }); module.exports = app; - diff --git a/controllers/users.js b/controllers/users.js index 004c58795c0..65b7a413464 100644 --- a/controllers/users.js +++ b/controllers/users.js @@ -9,7 +9,7 @@ const User = require('../models/user'); const { SECRET_KEY } = process.env; const avatarsDir = path.join(__dirname, '../public/avatars'); -// ✅ Rejestracja użytkownika +// Rejestracja użytkownika const signup = async (req, res) => { const { email, password } = req.body; const user = await User.findOne({ email }); @@ -19,7 +19,7 @@ const signup = async (req, res) => { } const hashedPassword = await bcrypt.hash(password, 10); - const avatarURL = gravatar.url(email); + const avatarURL = gravatar.url(email, { s: '250' }); // Generowanie awatara z gravatar const newUser = await User.create({ email, @@ -31,11 +31,12 @@ const signup = async (req, res) => { user: { email: newUser.email, subscription: newUser.subscription, + avatarURL: newUser.avatarURL, }, }); }; -// ✅ Logowanie użytkownika +// Logowanie użytkownika const login = async (req, res) => { const { email, password } = req.body; const user = await User.findOne({ email }); @@ -53,24 +54,25 @@ const login = async (req, res) => { user: { email: user.email, subscription: user.subscription, + avatarURL: user.avatarURL, }, }); }; -// ✅ Wylogowanie użytkownika +// Wylogowanie użytkownika const logout = async (req, res) => { const { _id } = req.user; await User.findByIdAndUpdate(_id, { token: null }); res.status(204).send(); }; -// ✅ Bieżący użytkownik +// Pobranie danych aktualnego użytkownika const getCurrentUser = async (req, res) => { - const { email, subscription } = req.user; - res.json({ email, subscription }); + const { email, subscription, avatarURL } = req.user; + res.json({ email, subscription, avatarURL }); }; -// ✅ Aktualizacja awatara +// Aktualizacja awatara const updateAvatar = async (req, res) => { try { if (!req.file) { @@ -98,17 +100,12 @@ const updateAvatar = async (req, res) => { res.json({ avatarURL: `${req.protocol}://${req.get('host')}${avatarURL}`, - user: { - email: updatedUser.email, - subscription: updatedUser.subscription, - }, }); } catch (error) { res.status(500).json({ message: 'Błąd podczas aktualizacji awatara.' }); } }; -// ✅ Eksportowanie funkcji module.exports = { signup, login, diff --git a/middlewares/auth.js b/middlewares/auth.js index 524407042c8..679a838098e 100644 --- a/middlewares/auth.js +++ b/middlewares/auth.js @@ -22,9 +22,9 @@ const authenticate = async (req, res, next) => { req.user = user; next(); } catch (error) { - console.error('Auth error:', error.message); return res.status(401).json({ message: 'Not authorized' }); } }; module.exports = authenticate; + diff --git a/middlewares/upload.js b/middlewares/upload.js index e16c41f7626..ef87ace4cf9 100644 --- a/middlewares/upload.js +++ b/middlewares/upload.js @@ -1,13 +1,11 @@ const multer = require('multer'); const path = require('path'); -// Folder tymczasowy do przechowywania przesłanych plików const tempDir = path.join(__dirname, '../tmp'); -// Konfiguracja Multer const storage = multer.diskStorage({ destination: (req, file, cb) => { - cb(null, tempDir); // Zapis w folderze tmp + cb(null, tempDir); }, filename: (req, file, cb) => { const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); @@ -16,7 +14,6 @@ const storage = multer.diskStorage({ } }); -// Filtr plików - akceptujemy tylko obrazy const fileFilter = (req, file, cb) => { if (file.mimetype.startsWith('image/')) { cb(null, true); diff --git a/models/user.js b/models/user.js index 56725a55fdb..735747c2949 100644 --- a/models/user.js +++ b/models/user.js @@ -19,6 +19,10 @@ const userSchema = new Schema({ type: String, default: null, }, + avatarURL: { + type: String, + default: '', + }, }, { versionKey: false, timestamps: true }); const User = model('user', userSchema); diff --git a/routes/api/users.js b/routes/api/users.js index 65e6fd7b8c1..c58a0b0f85e 100644 --- a/routes/api/users.js +++ b/routes/api/users.js @@ -1,7 +1,6 @@ const express = require('express'); const authenticate = require('../../middlewares/auth'); const upload = require('../../middlewares/upload'); - const { signup, login, diff --git a/server.js b/server.js index db330824656..73e6c9bffde 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,5 @@ -const app = require('./app') +const app = require('./app'); app.listen(3000, () => { - console.log("Server running. Use our API on port: 3000") -}) + console.log("Server running. Use our API on port: 3000"); +});