Browse Source

FIX: API user dan Blog with multiple Image

master
Wendy Gardiel 3 weeks ago
parent
commit
191a8c9967
  1. 3
      blog.rest
  2. 32
      controllers/RefreshToken.js
  3. 105
      controllers/blogController.js
  4. 114
      controllers/userController.js
  5. 24
      index.js
  6. 36
      middleware/UploadImage.js
  7. 13
      middleware/VerifyToken.js
  8. 25
      models/blogModel.js
  9. 27
      models/userModel.js
  10. 1170
      package-lock.json
  11. 5
      package.json
  12. 20
      routes/blogRoutes.js
  13. 19
      routes/index.js
  14. 18
      routes/userRoutes.js
  15. BIN
      uploads/images-1743046783320-gambar pohon.jpg
  16. 25
      user.rest

3
blog.rest

@ -1,5 +1,6 @@
POST http://localhost:5000/blog/
POST http://localhost:5000/blog/blog
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsIm5hbWUiOiJhZG1pbiIsImVtYWlsIjoiYWRtaW5AYWRtaW4uY29tIiwiaWF0IjoxNzQzMDQ1NjA0LCJleHAiOjE3NDMwNDU2MjR9.DnR5lQCXYOAF0NuLkLCxBRhZctnmw_jg9dfmN9R_zY0
{
"title": "GSI JAYA",

32
controllers/RefreshToken.js

@ -0,0 +1,32 @@
import Users from "../models/userModel.js";
import jwt from "jsonwebtoken";
export const refreshToken = async (req, res) => {
try {
const refreshToken = req.cookies.refreshToken;
if (!refreshToken) return res.sendStatus(401);
const user = await Users.findOne({
where: { refresh_token: refreshToken },
});
if (!user) return res.sendStatus(403);
jwt.verify(
refreshToken,
process.env.REFRESH_TOKEN_SECRET,
(err, decoded) => {
if (err) return res.sendStatus(403);
const accessToken = jwt.sign(
{ userId: user.id, name: user.name, email: user.email },
process.env.ACCESS_TOKEN_SECRET,
{ expiresIn: "20s" }
);
res.json({ accessToken });
}
);
} catch (error) {
res.status(500).json({ msg: "Terjadi kesalahan pada server" });
}
};

105
controllers/blogController.js

@ -3,63 +3,84 @@ import blogImage from "../models/blogImage.js";
// Get all blogs with images
export const getAllBlogs = async (req, res) => {
try {
const blogs = await blogModel.findAll({
include: [{ model: blogImage }]
});
res.json(blogs);
} catch (error) {
res.status(500).json({ message: error.message });
}
try {
const blogs = await blogModel.findAll({
include: [{ model: blogImage, as: "images" }], // Tambahkan alias "images"
});
res.json(blogs);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Get a single blog by ID
export const getBlogById = async (req, res) => {
try {
const blog = await blogModel.findByPk(req.params.id, {
include: [{ model: blogImage }]
});
if (!blog) return res.status(404).json({ message: "Blog not found" });
res.json(blog);
} catch (error) {
res.status(500).json({ message: error.message });
}
try {
const blog = await blogModel.findByPk(req.params.id, {
include: [{ model: blogImage, as: "images" }], // Tambahkan alias "images"
});
if (!blog) return res.status(404).json({ message: "Blog not found" });
res.json(blog);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Create a new blog
export const createBlog = async (req, res) => {
const { title, description, images } = req.body;
try {
const blog = await blogModel.create({ title, description });
if (images && images.length > 0) {
await blogImage.bulkCreate(images.map(imageUrl => ({ blogId: blog.id, imageUrl })));
}
res.status(201).json(blog);
} catch (error) {
res.status(500).json({ message: error.message });
try {
const { title, description } = req.body;
// Pastikan ada file yang diunggah
if (!req.files || req.files.length === 0) {
return res.status(400).json({ message: "Minimal 1 gambar diperlukan!" });
}
// Simpan data blog
const blog = await blogModel.create({ title, description });
// Simpan gambar ke database
const imagePaths = req.files.map((file) => ({
blogId: blog.id,
imageUrl: `/uploads/${file.filename}`,
}));
await blogImage.bulkCreate(imagePaths);
// Ambil data blog beserta gambar setelah disimpan
const newBlog = await blogModel.findByPk(blog.id, {
include: [{ model: blogImage, as: "images" }], // Pastikan alias sesuai
});
res.status(201).json({
message: "Blog created successfully",
blog: newBlog,
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Update a blog
export const updateBlog = async (req, res) => {
try {
const blog = await blogModel.findByPk(req.params.id);
if (!blog) return res.status(404).json({ message: "Blog not found" });
await blog.update(req.body);
res.json(blog);
} catch (error) {
res.status(500).json({ message: error.message });
}
try {
const blog = await blogModel.findByPk(req.params.id);
if (!blog) return res.status(404).json({ message: "Blog not found" });
await blog.update(req.body);
res.json(blog);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Delete a blog
export const deleteBlog = async (req, res) => {
try {
const blog = await blogModel.findByPk(req.params.id);
if (!blog) return res.status(404).json({ message: "Blog not found" });
await blog.destroy();
res.json({ message: "Blog deleted successfully" });
} catch (error) {
res.status(500).json({ message: error.message });
}
try {
const blog = await blogModel.findByPk(req.params.id);
if (!blog) return res.status(404).json({ message: "Blog not found" });
await blog.destroy();
res.json({ message: "Blog deleted successfully" });
} catch (error) {
res.status(500).json({ message: error.message });
}
};

114
controllers/userController.js

@ -0,0 +1,114 @@
import Users from "../models/userModel.js";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
//Get List User from database
export const getUsers = async (req, res) => {
try {
const users = await Users.findAll({
attributes: ["id", "name", "email"],
});
res.json(users);
} catch (error) {
console.log(error);
}
};
export const Register = async (req, res) => {
const { name, email, password, confirmPassword } = req.body;
if (password !== confirmPassword)
return res.status(400).json({ msg: "Password don't match" });
// Check if name or email already exists
const existingUser = await Users.findOne({ where: { email: email } });
if (existingUser) {
return res.status(400).json({ msg: "Email already exists" });
}
//Hash Password with bcrypt with Register account
const salt = await bcrypt.genSalt();
const hashPassword = await bcrypt.hash(password, salt);
try {
await Users.create({
name: name,
email: email,
password: hashPassword,
});
res.status(201).json({ msg: "Account Created", user: { name, email } });
} catch (error) {
res.status(400).json({ msg: "Failed to create Account" });
}
};
// Login user
export const Login = async (req, res) => {
try {
const user = await Users.findOne({
where: {
email: req.body.email,
},
});
if (!user) {
return res.status(404).json({ msg: "Email tidak ditemukan" });
}
// Perbaiki akses password (user.password bukan user[0].password)
const match = await bcrypt.compare(req.body.password, user.password);
if (!match) return res.status(400).json({ msg: "Wrong Password" });
const userId = user.id;
const name = user.name;
const email = user.email;
const accessToken = jwt.sign(
{ userId, name, email },
process.env.ACCESS_TOKEN_SECRET,
{ expiresIn: "1d" }
);
const refreshToken = jwt.sign(
{ userId, name, email },
process.env.REFRESH_TOKEN_SECRET,
{ expiresIn: "1d" }
);
await Users.update(
{ refresh_token: refreshToken },
{ where: { id: userId } }
);
res.cookie("refreshToken", refreshToken, {
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000,
});
res.json({ accessToken });
} catch (error) {
console.error("Error saat login:", error);
res.status(500).json({ msg: "Internal Server Error" });
}
};
export const Logout = async (req, res) => {
const refreshToken = req.cookies.refreshToken;
if (!refreshToken) return res.sendStatus(204);
const user = await Users.findAll({
where: {
refresh_token: refreshToken,
},
});
if (!user[0]) return res.json.sendStatus(204);
const userId = user[0].id;
await Users.update(
{ refresh_token: null },
{
where: {
id: userId,
},
}
);
res.clearCookie("refreshToken");
return res.sendStatus(200);
};

24
index.js

@ -1,23 +1,25 @@
import express from "express";
import db from "./config/database.js";
import blogRoutes from "./routes/index.js"
// import Blog from "./models/blogModel.js";
// import BlogImage from "./models/blogImage.js";
import dotenv from "dotenv";
import router from "./routes/index.js"; // Hanya import 1 router
import cors from "cors";
const app = express();
dotenv.config();
try {
// await db.sync();
const connectToDatabase = async () => {
try {
await db.authenticate();
console.log('Database connected...');
} catch(error){
console.log('Connection error:', error);
console.log("Database connected...");
} catch (error) {
console.log("Connection error:", error);
}
};
connectToDatabase();
app.use(cors());
app.use(express.json());
app.use ('/blog', blogRoutes);
app.use(router);
app.listen(5000,() => console.log('server running at port 5000'));
app.listen(5000, () => console.log("server running at port 5000"));

36
middleware/UploadImage.js

@ -0,0 +1,36 @@
import multer from "multer";
import path from "path";
// Konfigurasi storage
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, "uploads/"); // Folder penyimpanan
},
filename: (req, file, cb) => {
cb(null, file.fieldname + "-" + Date.now() + "-" + file.originalname);
},
});
// Filter jenis file
const fileFilter = (req, file, cb) => {
const allowedTypes = /jpeg|jpg|png|gif/;
const extname = allowedTypes.test(
path.extname(file.originalname).toLowerCase()
);
const mimetype = allowedTypes.test(file.mimetype);
if (mimetype && extname) {
return cb(null, true);
} else {
cb(new Error("Only images (jpeg, jpg, png, gif) are allowed"));
}
};
//for multiple images
const upload = multer({
storage: storage,
limits: { fileSize: 5 * 1024 * 1024 }, // Maksimal 5MB per file
fileFilter: fileFilter,
});
export default upload;

13
middleware/VerifyToken.js

@ -0,0 +1,13 @@
import jwt from "jsonwebtoken";
export const verifyToken = (req, res, next) => {
const authHeader = req.headers["authorization"];
const token = authHeader && authHeader.split(" ")[1];
if (token == null) return res.sendStatus(401);
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
};

25
models/blogModel.js

@ -1,17 +1,22 @@
import { Sequelize } from "sequelize";
import db from "../config/database.js";
import blogImage from "./blogImage.js";
const { DataTypes } = Sequelize;
const Blog = db.define('blogs', {
title:{
type: DataTypes.STRING
},
description:{
type: DataTypes.TEXT
}
},{
freezeTableName:true
const Blog = db.define("blog", {
title: {
type: DataTypes.STRING,
allowNull: false,
},
description: {
type: DataTypes.TEXT,
allowNull: false,
},
});
export default Blog;
// Definisi relasi dengan alias 'images'
Blog.hasMany(blogImage, { foreignKey: "blogId", as: "images" });
blogImage.belongsTo(Blog, { foreignKey: "blogId", as: "blog" });
export default Blog;

27
models/userModel.js

@ -0,0 +1,27 @@
import { Sequelize } from "sequelize";
import db from "../config/database.js";
const { DataTypes } = Sequelize;
const User = db.define(
"users",
{
name: {
type: DataTypes.STRING,
},
email: {
type: DataTypes.STRING,
},
password: {
type: DataTypes.STRING,
},
refresh_token: {
type: DataTypes.TEXT,
},
},
{
freezeTableName: true,
}
);
export default User;

1170
package-lock.json
File diff suppressed because it is too large
View File

5
package.json

@ -10,9 +10,14 @@
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.2",
"mysql2": "^3.12.0",
"nodemon": "^3.1.9",
"sequelize": "^6.37.5"
}
}

20
routes/blogRoutes.js

@ -0,0 +1,20 @@
import express from "express";
import {
getAllBlogs,
getBlogById,
createBlog,
updateBlog,
deleteBlog,
} from "../controllers/blogController.js";
import upload from "../middleware/UploadImage.js";
import { verifyToken } from "../middleware/VerifyToken.js";
const router = express.Router();
router.get("/blog", getAllBlogs);
router.get("/blog/:id", getBlogById);
router.post("/blog", verifyToken, upload.array("images", 5), createBlog);
router.patch("/blog/:id", verifyToken, updateBlog);
router.delete("/blog/:id", verifyToken, deleteBlog);
export default router;

19
routes/index.js

@ -1,19 +1,10 @@
import express from "express";
import {
getAllBlogs,
getBlogById,
createBlog,
updateBlog,
deleteBlog
} from "../controllers/blogController.js"
import blogRoutes from "./blogRoutes.js";
import userRoutes from "./userRoutes.js";
const router = express.Router();
router.get('/', getAllBlogs);
router.get('/:id', getBlogById);
router.post('/', createBlog);
router.patch('/:id', updateBlog);
router.delete('/:id', deleteBlog);
router.use("/blog", blogRoutes);
router.use("/users", userRoutes);
export default router;
export default router;

18
routes/userRoutes.js

@ -0,0 +1,18 @@
import express from "express";
import {
getUsers,
Register,
Login,
Logout,
} from "../controllers/userController.js";
import { refreshToken } from "../controllers/RefreshToken.js";
import { verifyToken } from "../middleware/VerifyToken.js";
const router = express.Router();
router.get("/", verifyToken, getUsers);
router.get("/token", refreshToken);
router.post("/register", Register);
router.post("/login", Login);
router.post("/logout", Logout);
export default router;

BIN
uploads/images-1743046783320-gambar pohon.jpg

After

Width: 1280  |  Height: 720  |  Size: 90 KiB

25
user.rest

@ -0,0 +1,25 @@
POST http://localhost:5000/user/register
Content-type: application/json
{
"name" : "admin2",
"email" : "admin2@admin.com",
"password" : "admin123",
"confirmPassword" : "admin123"
}
###
POST http://localhost:5000/users/login/
Content-Type: application/json
{
"email": "admin@admin.com",
"password": "admin123"
}
###
GET http://localhost:5000/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsIm5hbWUiOiJhZG1pbiIsImVtYWlsIjoiYWRtaW5AYWRtaW4uY29tIiwiaWF0IjoxNzQzMDQ0NDg5LCJleHAiOjE3NDMwNDQ1MDl9.m_tpB5DPpFDIaSWQki8jCKWqCbSzsBaKqh3W35a32XI
|||||||
100:0
Loading…
Cancel
Save