news-cms-module 0.1.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,45 @@
1
+ const multer = require('multer');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+
5
+ const storage = multer.diskStorage({
6
+ destination: function (req, file, cb) {
7
+ let dest = 'public/newspicture/';
8
+
9
+ if (file.fieldname === 'thumbnailImage') {
10
+ dest += 'thumbnail';
11
+ } else if (file.fieldname === 'contentImages') {
12
+ dest += 'contentnews';
13
+ }
14
+
15
+ if (!fs.existsSync(dest)) {
16
+ fs.mkdirSync(dest, { recursive: true });
17
+ }
18
+
19
+ cb(null, dest);
20
+ },
21
+ filename: function (req, file, cb) {
22
+ const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
23
+ cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
24
+ }
25
+ });
26
+
27
+ const fileFilter = (req, file, cb) => {
28
+ const allowedTypes = /jpeg|jpg|png|webp/;
29
+ const isExtensionValid = allowedTypes.test(path.extname(file.originalname).toLowerCase());
30
+ const isMimeValid = allowedTypes.test(file.mimetype);
31
+
32
+ if (isExtensionValid && isMimeValid) {
33
+ cb(null, true);
34
+ } else {
35
+ cb(new Error('Format file tidak didukung! Gunakan jpg/jpeg/png.'), false);
36
+ }
37
+ };
38
+
39
+ const upload = multer({
40
+ storage: storage,
41
+ fileFilter: fileFilter,
42
+ limits: { fileSize: 3 * 1024 * 1024 }
43
+ });
44
+
45
+ module.exports = upload;
@@ -0,0 +1,15 @@
1
+ const parseContentBlocks = (req, res, next) => {
2
+ if (req.body.contentBlocks && typeof req.body.contentBlocks === 'string') {
3
+ try {
4
+ req.body.contentBlocks = JSON.parse(req.body.contentBlocks);
5
+ } catch (e) {
6
+ return res.status(400).json({ success: false, error: 'Format konten tidak valid' });
7
+ }
8
+ }
9
+ console.log("di parseform");
10
+ next();
11
+ };
12
+
13
+ module.exports = {
14
+ parseContentBlocks
15
+ }
package/models/News.js CHANGED
@@ -17,6 +17,7 @@ module.exports = (sequelize) => {
17
17
  type: DataTypes.STRING(255),
18
18
  allowNull: false,
19
19
  unique: true,
20
+ unique: 'unique_slug_index'
20
21
  },
21
22
  authorName: {
22
23
  type: DataTypes.STRING(50),
package/models/index.js CHANGED
@@ -1,16 +1,13 @@
1
- // models/index.js
2
-
3
1
  const { Sequelize } = require('sequelize');
4
2
  const NewsModel = require('./News');
5
3
  const ContentNewsModel = require('./ContentNews');
6
4
  const VisitorLogModel = require('./VisitorLog');
7
5
 
8
6
  module.exports = (config) => {
9
- // Inisialisasi Sequelize dengan konfigurasi dari aplikasi pengguna
10
7
  const sequelize = new Sequelize(config.database, config.username, config.password, {
11
8
  host: config.host,
12
9
  dialect: 'mysql',
13
- logging: false, // Matikan logging SQL
10
+ logging: false,
14
11
  });
15
12
 
16
13
  const db = {};
@@ -22,7 +19,7 @@ module.exports = (config) => {
22
19
 
23
20
  db.News.hasMany(db.ContentNews, {
24
21
  foreignKey: 'newsId',
25
- as: 'blocks',
22
+ as: 'contentBlocks',
26
23
  onDelete: 'CASCADE'
27
24
  });
28
25
  db.ContentNews.belongsTo(db.News, {
@@ -30,7 +27,6 @@ module.exports = (config) => {
30
27
  as: 'newsItem'
31
28
  });
32
29
 
33
-
34
30
  db.News.hasMany(db.VisitorLog, {
35
31
  foreignKey: 'newsId',
36
32
  as: 'visits',
@@ -42,7 +38,6 @@ module.exports = (config) => {
42
38
  });
43
39
 
44
40
 
45
- // --- PENAMBAHAN KUNCI UNTUK SINKRONISASI ---
46
41
  /**
47
42
  * Metode untuk mensinkronisasi model dengan tabel database.
48
43
  * @param {object} options - Opsi sinkronisasi Sequelize (e.g., { alter: true } atau { force: true }).
@@ -52,7 +47,7 @@ module.exports = (config) => {
52
47
  // dan mencoba mengubah kolom yang ada agar sesuai dengan definisi model, tanpa menghapus data.
53
48
  await sequelize.sync(options);
54
49
  };
55
- // ----------------------------------------------
50
+
56
51
 
57
52
  db.sequelize = sequelize;
58
53
  db.Sequelize = Sequelize;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "news-cms-module",
3
- "version": "0.1.1",
3
+ "version": "1.0.0",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -13,6 +13,9 @@
13
13
  "dotenv": "^17.2.3",
14
14
  "ejs": "^3.1.10",
15
15
  "express": "^5.1.0",
16
+ "express-session": "^1.18.2",
17
+ "express-validator": "^7.3.1",
18
+ "multer": "^2.0.2",
16
19
  "mysql2": "^3.15.1",
17
20
  "path": "^0.12.7",
18
21
  "sequelize": "^6.37.7"
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,92 @@
1
+ // Search Bar Toggle Start
2
+ let navbar = document.querySelector(".navbar");
3
+ let searchBar = document.querySelector(".search-bar .fa-magnifying-glass");
4
+
5
+ if (searchBar) {
6
+ searchBar.addEventListener("click", () => {
7
+ navbar.classList.toggle("showInput");
8
+
9
+ if (navbar.classList.contains("showInput")) {
10
+ searchBar.classList.replace("fa-magnifying-glass", "fa-x");
11
+ } else {
12
+ searchBar.classList.replace("fa-x", "fa-magnifying-glass");
13
+ }
14
+ });
15
+ }
16
+ // Search Bar Toggle End
17
+
18
+ // Menu Toggle Start
19
+ let menuIcon = document.querySelector("#menu-icon");
20
+ let menu = document.querySelector(".menu");
21
+
22
+ if (menuIcon) {
23
+ menuIcon.addEventListener("click", () => {
24
+ menu.classList.toggle("active");
25
+
26
+ // Toggle icon between bars and x
27
+ if (menu.classList.contains("active")) {
28
+ menuIcon.classList.replace("fa-bars", "fa-x");
29
+ } else {
30
+ menuIcon.classList.replace("fa-x", "fa-bars");
31
+ }
32
+ });
33
+ }
34
+ // Menu Toggle End
35
+
36
+ // Dropdown Toggle Start
37
+ document.addEventListener("DOMContentLoaded", () => {
38
+ const dropdowns = document.querySelectorAll(".dropdown");
39
+
40
+ dropdowns.forEach((dropdown) => {
41
+ const toggle = dropdown.querySelector(".dropdown-toggle");
42
+ const menu = dropdown.querySelector(".dropdown-menu");
43
+
44
+ if (toggle && menu) {
45
+ toggle.addEventListener("click", (e) => {
46
+ e.preventDefault();
47
+ e.stopPropagation();
48
+
49
+ // Close other dropdowns
50
+ dropdowns.forEach((other) => {
51
+ if (other !== dropdown) {
52
+ other.classList.remove("active");
53
+ other.querySelector(".dropdown-menu")?.classList.remove("show");
54
+ }
55
+ });
56
+
57
+ // Toggle current
58
+ dropdown.classList.toggle("active");
59
+ menu.classList.toggle("show");
60
+ });
61
+ }
62
+ });
63
+
64
+ // Close dropdown when clicking outside
65
+ document.addEventListener("click", (e) => {
66
+ dropdowns.forEach((dropdown) => {
67
+ if (!dropdown.contains(e.target)) {
68
+ dropdown.classList.remove("active");
69
+ dropdown.querySelector(".dropdown-menu")?.classList.remove("show");
70
+ }
71
+ });
72
+ });
73
+ });
74
+ // Dropdown Toggle End
75
+
76
+ // Authentication Check Start
77
+ document.addEventListener("DOMContentLoaded", () => {
78
+ const loginBtn = document.querySelector(".btn-login");
79
+ const isLoggedIn = localStorage.getItem("isLoggedIn");
80
+
81
+ if (loginBtn && isLoggedIn === "true") {
82
+ loginBtn.textContent = "Logout";
83
+ loginBtn.href = "#"; // Prevent immediate redirect
84
+
85
+ loginBtn.addEventListener("click", (e) => {
86
+ e.preventDefault();
87
+ localStorage.removeItem("isLoggedIn");
88
+ window.location.href = "./login.html";
89
+ });
90
+ }
91
+ });
92
+ // Authentication Check End