Compare commits

...

No commits in common. 'main' and 'master' have entirely different histories.
main ... master

  1. 1
      .gitignore
  2. 2
      README.md
  3. 8
      blog.rest
  4. 16
      config/database.js
  5. 32
      controllers/RefreshToken.js
  6. 123
      controllers/blogController.js
  7. 114
      controllers/userController.js
  8. 30
      index.js
  9. 36
      middleware/UploadImage.js
  10. 13
      middleware/VerifyToken.js
  11. 24
      models/blogImage.js
  12. 27
      models/blogModel.js
  13. 27
      models/userModel.js
  14. 2401
      package-lock.json
  15. 24
      package.json
  16. 22
      routes/blogRoutes.js
  17. 10
      routes/index.js
  18. 18
      routes/userRoutes.js
  19. BIN
      uploads/images-1743046783320-gambar pohon.jpg
  20. 24
      user.rest

1
.gitignore

@ -116,3 +116,4 @@ dist
.yarn/install-state.gz
.pnp.*
uploads/

2
README.md

@ -1,2 +0,0 @@
# ComproGSI_BE

8
blog.rest

@ -0,0 +1,8 @@
POST http://localhost:5000/blog/blog
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsIm5hbWUiOiJhZG1pbiIsImVtYWlsIjoiYWRtaW5AYWRtaW4uY29tIiwiaWF0IjoxNzQzMDQ1NjA0LCJleHAiOjE3NDMwNDU2MjR9.DnR5lQCXYOAF0NuLkLCxBRhZctnmw_jg9dfmN9R_zY0
{
"title": "GSI JAYA",
"description" : "lorem uasdasdadasdasdad"
}

16
config/database.js

@ -0,0 +1,16 @@
import { Sequelize } from "sequelize";
import dotenv from "dotenv";
dotenv.config();
const db = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASSWORD,
{
host: process.env.DB_HOST,
dialect: process.env.DB_DIALECT,
}
);
export default db;

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" });
}
};

123
controllers/blogController.js

@ -0,0 +1,123 @@
import blogModel from "../models/blogModel.js";
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, 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, 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 });
}
};
// Get blog by slug
export const getBlogBySlug = async (req, res) => {
try {
const blog = await blogModel.findOne({
where: { slug: req.params.slug },
include: [{ model: blogImage, as: "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) => {
try {
const { title, description } = req.body;
if (!req.files || req.files.length === 0) {
return res.status(400).json({ message: "Minimal 1 gambar diperlukan!" });
}
const slug = generateSlug(title); // 🔥 generate slug di sini
const blog = await blogModel.create({ title, description, slug });
const imagePaths = req.files.map((file) => ({
blogId: blog.id,
imageUrl: `/uploads/${file.filename}`,
}));
await blogImage.bulkCreate(imagePaths);
const newBlog = await blogModel.findByPk(blog.id, {
include: [{ model: blogImage, as: "images" }],
});
res.status(201).json({
message: "Blog created successfully",
blog: newBlog,
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const generateSlug = (title) => {
return title
.toLowerCase()
.replace(/\s+/g, "-")
.replace(/[^\w-]+/g, "");
};
// 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" });
const { title, description, slug } = req.body;
// Tentukan slug baru (jika ada title baru dan tidak ada slug manual)
let updatedSlug = blog.slug; // default: slug lama
if (title && !slug) {
updatedSlug = generateSlug(title);
} else if (slug) {
updatedSlug = slug;
}
// Update data
await blog.update({
title: title ?? blog.title,
description: description ?? blog.description,
slug: updatedSlug,
});
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 });
}
};

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);
};

30
index.js

@ -0,0 +1,30 @@
import express from "express";
import db from "./config/database.js";
import dotenv from "dotenv";
import router from "./routes/index.js"; // Hanya import 1 router
import cors from "cors";
import cookieParser from "cookie-parser";
import path from "path";
const app = express();
dotenv.config();
const connectToDatabase = async () => {
// await db.sync({ force: true });
try {
await db.authenticate();
console.log("Database connected...");
} catch (error) {
console.log("Connection error:", error);
}
};
connectToDatabase();
app.use(cookieParser());
app.use(cors());
app.use(express.json());
app.use(router);
app.use("/uploads", express.static(path.join(process.cwd(), "uploads")));
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();
});
};

24
models/blogImage.js

@ -0,0 +1,24 @@
import { Sequelize } from "sequelize";
import db from "../config/database.js";
const { DataTypes } = Sequelize;
const BlogImage = db.define("blog_images", {
blogId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: "blogs",
key: "id"
},
onDelete: "CASCADE"
},
imageUrl: {
type: DataTypes.STRING,
allowNull: false
}
}, {
freezeTableName: true
});
export default BlogImage;

27
models/blogModel.js

@ -0,0 +1,27 @@
import { Sequelize } from "sequelize";
import db from "../config/database.js";
import blogImage from "./blogImage.js";
const { DataTypes } = Sequelize;
const Blog = db.define("blog", {
title: {
type: DataTypes.STRING,
allowNull: false,
},
description: {
type: DataTypes.TEXT,
allowNull: false,
},
slug: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
});
// 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;

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

24
package.json

@ -0,0 +1,24 @@
{
"name": "becompro",
"version": "1.0.0",
"description": "",
"license": "ISC",
"author": "arfan",
"type": "module",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"bcrypt": "^5.1.1",
"cookie-parser": "^1.4.7",
"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"
}
}

22
routes/blogRoutes.js

@ -0,0 +1,22 @@
import express from "express";
import {
getAllBlogs,
getBlogById,
getBlogBySlug,
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.get("/blog/slug/:slug", getBlogBySlug);
router.post("/blog", upload.array("images", 5), createBlog);
router.put("/blog/:id", upload.array("images", 5), updateBlog);
router.delete("/blog/:id", deleteBlog);
export default router;

10
routes/index.js

@ -0,0 +1,10 @@
import express from "express";
import blogRoutes from "./blogRoutes.js";
import userRoutes from "./userRoutes.js";
const router = express.Router();
router.use("/blog", blogRoutes);
router.use("/users", userRoutes);
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

24
user.rest

@ -0,0 +1,24 @@
POST http://localhost:5000/users/register
Content-type: application/json
{
"name" : "admin",
"email" : "admin@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/token
|||||||
100:0
Loading…
Cancel
Save