From 0c46c6f76585d352af5a99f24f0872f1719006d2 Mon Sep 17 00:00:00 2001 From: Kacpero60 Date: Mon, 10 Feb 2025 17:11:13 +0100 Subject: [PATCH 1/3] 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 805d4563ae67ed0aed872e5f2703bfc3055e1ce7 Mon Sep 17 00:00:00 2001 From: Kacpero60 Date: Mon, 10 Feb 2025 17:24:07 +0100 Subject: [PATCH 2/3] zadanie 5 --- app.js | 9 +- controllers/users.js | 34 ++++++ middlewares/upload.js | 30 +++++ package-lock.json | 268 ++++++++++++++++++++++++++++++++++++++++-- package.json | 3 +- routes/api/users.js | 9 ++ 6 files changed, 342 insertions(+), 11 deletions(-) create mode 100644 controllers/users.js create mode 100644 middlewares/upload.js create mode 100644 routes/api/users.js diff --git a/app.js b/app.js index 461994c15fa..df54cda8806 100644 --- a/app.js +++ b/app.js @@ -1,22 +1,29 @@ const express = require('express'); const cors = require('cors'); +const path = require('path'); require('dotenv').config(); const contactsRouter = require('./routes/api/contacts'); const app = express(); +// Middleware app.use(cors()); app.use(express.json()); +// 📦 Serwowanie statycznych plików z folderu public/avatars +app.use('/avatars', express.static(path.join(__dirname, 'public/avatars'))); + +// Routes app.use('/api/contacts', contactsRouter); +// Obsługa błędu 404 app.use((req, res) => { res.status(404).json({ message: 'Not found' }); }); +// Obsługa błędów app.use((err, req, res, next) => { res.status(500).json({ message: err.message }); }); module.exports = app; - diff --git a/controllers/users.js b/controllers/users.js new file mode 100644 index 00000000000..eae904d6b96 --- /dev/null +++ b/controllers/users.js @@ -0,0 +1,34 @@ +const fs = require('fs/promises'); +const path = require('path'); +const jimp = require('jimp'); +const User = require('../models/user'); // Model użytkownika + +const avatarsDir = path.join(__dirname, '../public/avatars'); + +const updateAvatar = async (req, res) => { + try { + const { path: tempUpload, originalname } = req.file; + const { _id } = req.user; + + const uniqueName = `${_id}-${Date.now()}${path.extname(originalname)}`; + const resultUpload = path.join(avatarsDir, uniqueName); + + // Obróbka zdjęcia przy użyciu jimp + const image = await jimp.read(tempUpload); + await image.resize(250, 250).writeAsync(resultUpload); + + // Usuwanie pliku tymczasowego + await fs.unlink(tempUpload); + + const avatarURL = `/avatars/${uniqueName}`; + + // Aktualizacja w bazie danych + await User.findByIdAndUpdate(_id, { avatarURL }); + + res.json({ avatarURL }); + } catch (error) { + res.status(500).json({ message: 'Błąd podczas aktualizacji awatara.' }); + } +}; + +module.exports = { updateAvatar }; 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; diff --git a/package-lock.json b/package-lock.json index e6d047044e5..0925ce04bf7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "cors": "2.8.5", "cross-env": "7.0.3", "express": "4.17.1", - "morgan": "1.10.0" + "morgan": "1.10.0", + "multer": "^1.4.5-lts.1" }, "devDependencies": { "eslint": "7.19.0", @@ -278,6 +279,12 @@ "node": ">= 8" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -452,6 +459,23 @@ "node": ">=8" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -624,6 +648,21 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "node_modules/configstore": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", @@ -673,6 +712,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -2161,6 +2206,12 @@ "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", "dev": true }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2392,10 +2443,25 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } }, "node_modules/morgan": { "version": "1.10.0", @@ -2439,6 +2505,24 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -2825,6 +2909,12 @@ "node": ">=4" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -2943,6 +3033,21 @@ "node": ">=0.10.0" } }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -3222,6 +3327,23 @@ "node": ">= 0.6" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -3437,6 +3559,12 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -3536,6 +3664,12 @@ "node": ">=4" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -3653,6 +3787,15 @@ "node": ">=8" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -3858,6 +4001,11 @@ "picomatch": "^2.0.4" } }, + "append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -3997,6 +4145,19 @@ "fill-range": "^7.0.1" } }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "requires": { + "streamsearch": "^1.1.0" + } + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -4124,6 +4285,17 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "configstore": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", @@ -4161,6 +4333,11 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -5264,6 +5441,11 @@ "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", "dev": true }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -5443,10 +5625,17 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "requires": { + "minimist": "^1.2.6" + } }, "morgan": { "version": "1.10.0", @@ -5486,6 +5675,20 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "requires": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + } + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5763,6 +5966,11 @@ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", "dev": true }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -5856,6 +6064,20 @@ } } }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -6075,6 +6297,19 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -6240,6 +6475,11 @@ "mime-types": "~2.1.24" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -6321,6 +6561,11 @@ "prepend-http": "^2.0.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -6408,6 +6653,11 @@ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "dev": true }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 5045e827160..6cdb238fbff 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "cors": "2.8.5", "cross-env": "7.0.3", "express": "4.17.1", - "morgan": "1.10.0" + "morgan": "1.10.0", + "multer": "^1.4.5-lts.1" }, "devDependencies": { "eslint": "7.19.0", diff --git a/routes/api/users.js b/routes/api/users.js new file mode 100644 index 00000000000..d2de1cbc803 --- /dev/null +++ b/routes/api/users.js @@ -0,0 +1,9 @@ +const express = require('express'); +const upload = require('../../middlewares/upload'); // Import upload middleware +const { updateAvatar } = require('../../controllers/users'); // Funkcja kontrolera + +const router = express.Router(); + +router.patch('/avatars', upload.single('avatar'), updateAvatar); + +module.exports = router; From 810f21c730e0e8b58846afb0086f209a48e5d8e6 Mon Sep 17 00:00:00 2001 From: Kacpero60 Date: Tue, 11 Feb 2025 13:30:28 +0100 Subject: [PATCH 3/3] zadanie 4 poprawione z users --- controllers/users.js | 98 ++++++++++++++++++++++++++++++++++++++++---- middlewares/auth.js | 1 + models/user.js | 4 ++ routes/api/users.js | 18 ++++++-- 4 files changed, 111 insertions(+), 10 deletions(-) diff --git a/controllers/users.js b/controllers/users.js index eae904d6b96..004c58795c0 100644 --- a/controllers/users.js +++ b/controllers/users.js @@ -1,34 +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'); // Model użytkownika +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); - // Obróbka zdjęcia przy użyciu jimp const image = await jimp.read(tempUpload); await image.resize(250, 250).writeAsync(resultUpload); - // Usuwanie pliku tymczasowego await fs.unlink(tempUpload); const avatarURL = `/avatars/${uniqueName}`; - // Aktualizacja w bazie danych - await User.findByIdAndUpdate(_id, { avatarURL }); + const updatedUser = await User.findByIdAndUpdate( + _id, + { avatarURL }, + { new: true } + ); - res.json({ avatarURL }); + 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.' }); } }; -module.exports = { updateAvatar }; +// ✅ Eksportowanie funkcji +module.exports = { + signup, + login, + logout, + getCurrentUser, + updateAvatar, +}; 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' }); } }; 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 d2de1cbc803..65e6fd7b8c1 100644 --- a/routes/api/users.js +++ b/routes/api/users.js @@ -1,9 +1,21 @@ const express = require('express'); -const upload = require('../../middlewares/upload'); // Import upload middleware -const { updateAvatar } = require('../../controllers/users'); // Funkcja kontrolera +const authenticate = require('../../middlewares/auth'); +const upload = require('../../middlewares/upload'); + +const { + signup, + login, + logout, + getCurrentUser, + updateAvatar, +} = require('../../controllers/users'); const router = express.Router(); -router.patch('/avatars', upload.single('avatar'), updateAvatar); +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;