news-cms-module 1.0.0 → 1.1.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.
package/README.md CHANGED
@@ -21,48 +21,72 @@ npm install news-cms-module ejs express-session mysql2
21
21
 
22
22
  ```javascript
23
23
  const express = require('express');
24
- const path = require('path');
24
+ require('dotenv').config();
25
25
  const newsModule = require('news-cms-module');
26
26
 
27
27
  const app = express();
28
28
 
29
29
  const dbConfig = {
30
- database: 'dummy_news',// bisa disesuaikan lagi
31
- username: 'root',
32
- password: 'your_password',
33
- host: 'localhost',};
34
-
35
- const PORT = 3000;
30
+ host: process.env.DB_HOST,
31
+ port: process.env.DB_PORT || 3036, // Sesuaikan dengan port db anda
32
+ username: process.env.DB_USER_NAME,
33
+ password: process.env.DB_PASSWORD,
34
+ database: process.env.DB_NAME,
35
+ dialect: 'mysql',
36
+ dialectOptions: {
37
+ ssl: {
38
+ require: true,
39
+ rejectUnauthorized: false // Set ke false jika tidak pakai file sertifikat .pem
40
+ }
41
+ },
42
+ // Opsional: tambahkan ini untuk mencegah timeout di koneksi lambat
43
+ pool: {
44
+ max: 5,
45
+ min: 0,
46
+ acquire: 30000,
47
+ idle: 10000
48
+ }
49
+ };
50
+
51
+ // Konfigurasi URL Dinamis
52
+ const PORT = process.env.PORT || 3000;
53
+ const APP_URL = process.env.APP_URL || `http://localhost:${PORT}`;
54
+ const NEWS_PREFIX = '/berita'; // Anda bisa mengganti ini sesuka hati (misal: /news)
36
55
 
37
56
  async function startServer() {
38
- // 1. Middleware Global & View Engine
57
+ // 1. Inisialisasi Middleware Global
39
58
  app.use(express.json());
40
59
  app.use(express.urlencoded({ extended: true }));
41
-
42
- // Public asset untuk akses gambar berita
43
- app.use(express.static('public'))
44
-
45
- // WAJIB: Atur EJS agar tampilan modul dapat dirender
46
- app.set('view engine', 'ejs');
47
-
48
- // 2. Inisialisasi Modul News
49
- // Package dijalankan secara ASYNC karena sinkronisasi database
50
- const newsRouter = await newsModule(dbConfig, {
51
- adminRoutePrefix: '/cms-admin',
52
- sessionSecret: 'news_cms_secret_key'});
53
-
54
- // 3. Pasang Router ke Prefix URL Host
55
- app.use('/berita', newsRouter);
56
-
57
- // 4. Jalankan Server
58
- app.listen(PORT, () => {
59
- console.log(`Server running on port ${PORT}`);
60
- console.log(`User Interface: http://localhost:${PORT}/berita/list`);
61
- console.log(`Admin Dashboard: http://localhost:${PORT}/berita/cms-admin/dashboard`);
62
- });
60
+ app.use(express.static('public'));
61
+
62
+ try {
63
+ // 2. Inisialisasi Package (Dijalankan secara ASYNC)
64
+ const newsRouter = await newsModule(dbConfig, {
65
+ adminRoutePrefix: '/cms-admin',
66
+ newsPrefix: NEWS_PREFIX,
67
+ baseUrl: APP_URL + NEWS_PREFIX,
68
+ });
69
+
70
+ // 3. Pasang Router ke Prefix URL
71
+ // Ini akan membuat rute: /berita/list, /berita/cms-admin, dll.
72
+ app.use(NEWS_PREFIX, newsRouter);
73
+
74
+ // 4. Jalankan Server
75
+ app.listen(PORT, () => {
76
+ console.log(`Server Berhasil Dijalankan!`);
77
+ console.log(`Base URL : ${APP_URL}`);
78
+ console.log(`API Berita : ${APP_URL}${NEWS_PREFIX}/list`);
79
+ console.log(`Admin CMS : ${APP_URL}${NEWS_PREFIX}/cms-admin/dashboard`);
80
+ });
81
+ } catch (error) {
82
+ console.error("Gagal menjalankan server:", error);
83
+ }
63
84
  }
64
85
 
65
86
  startServer();
66
87
  ```
88
+ pastikan untuk menyesuaikan bagian authAdminMiddleware pada module di bagian middlewares. Sesuaikan dengan preferensi tabel user masing-masing.
89
+
67
90
 
68
- pastikan untuk menyesuaikan bagian authAdminMiddleware pada module di bagian middlewares. Sesuaikan dengan preferensi tabel user masing-masing.
91
+ github news-cms-module: https://github.com/DewaAry-D/news-cms-module.git
92
+ github contoh implementasi: https://github.com/DewaAry-D/dummy-news.git
package/config/index.js CHANGED
@@ -4,6 +4,8 @@ const defaultConfig = {
4
4
  // Pengaturan Router
5
5
  adminRoutePrefix: '/admin', // Prefix default untuk route CRUD admin
6
6
  publicRoutePrefix: '/', // Prefix default untuk route publik
7
+ newsPrefix: '/berita', // Prefix defaul umum contohnya /berita/{..}
8
+ baseURL: '', // Prefix defaul untuk fetch API
7
9
 
8
10
  // Pengaturan Views
9
11
  viewEngine: 'ejs', // Template engine default
@@ -37,24 +37,6 @@ class NewsController {
37
37
  const categories = await this.newsService.getUniqueCategories();
38
38
  const trending = await this.newsService.getTrendingNews();
39
39
 
40
- //ini mati klo dah ada view
41
- // res.status(200).json({
42
- // success: true,
43
- // data: {
44
- // posts,
45
- // categories,
46
- // trending,
47
- // pagination: {
48
- // totalItems,
49
- // totalPages,
50
- // currentPage,
51
- // perPage,
52
- // hasNextPage: currentPage < totalPages,
53
- // hasPrevPage: currentPage > 1
54
- // }
55
- // }
56
- // });
57
-
58
40
  //render
59
41
  res.render(path.join(__dirname, "../views/home.ejs"), {
60
42
  posts,
@@ -145,13 +127,17 @@ class NewsController {
145
127
  });
146
128
  }
147
129
 
148
- // res.status(200).json({
149
- // success: true,
150
- // data: post
151
- // })
130
+ const appBaseUrl = config.baseUrl;
131
+ const newsPrefix = config.newsPrefix;
132
+ const adminPrefix = config.adminRoutePrefix;
133
+
134
+ const fullApiUrl = `${appBaseUrl}${adminPrefix}/create`;
135
+ const nextUrl = `${newsPrefix}${adminPrefix}/list`
152
136
 
153
137
  res.render(path.join(__dirname, '../views/admin/update_news.ejs'), {
154
- data: posts
138
+ data: posts,
139
+ apiBaseUrl: fullApiUrl,
140
+ nextUrl
155
141
  });
156
142
  } catch (error) {
157
143
  res.status(500).json({
package/models/index.js CHANGED
@@ -6,8 +6,11 @@ const VisitorLogModel = require('./VisitorLog');
6
6
  module.exports = (config) => {
7
7
  const sequelize = new Sequelize(config.database, config.username, config.password, {
8
8
  host: config.host,
9
- dialect: 'mysql',
10
- logging: false,
9
+ port: config.port,
10
+ dialect: config.dialect || 'mysql',
11
+ dialectOptions: config.dialectOptions,
12
+ pool: config.pool,
13
+ logging: config.logging !== undefined ? config.logging : false,
11
14
  });
12
15
 
13
16
  const db = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "news-cms-module",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
package/routes/index.js CHANGED
@@ -42,8 +42,24 @@ module.exports = (router, services, config) => {
42
42
  const adminRouter = express.Router();
43
43
  adminRouter.use(isAdmin);
44
44
 
45
- adminRouter.get('/create', (req, res) => {
46
- res.render(path.join(__dirname, '../views/admin/create_news.ejs'));
45
+ // adminRouter.get('/create', (req, res) => {
46
+ // res.render(path.join(__dirname, '../views/admin/create_news.ejs'));
47
+ // });
48
+
49
+ router.get('/cms-admin/create', (req, res) => {
50
+ const appBaseUrl = config.baseUrl;
51
+ const newsPrefix = config.newsPrefix;
52
+ const adminPrefix = config.adminRoutePrefix;
53
+
54
+ const fullApiUrl = `${appBaseUrl}${adminPrefix}/create`;
55
+ const nextUrl = `${newsPrefix}${adminPrefix}/list`
56
+
57
+ res.render(path.join(__dirname, "../views/admin/create_news.ejs"), {
58
+ title: 'Buat Berita Baru',
59
+
60
+ apiBaseUrl: fullApiUrl,
61
+ nextUrl
62
+ });
47
63
  });
48
64
  adminRouter.post('/create', beritaUpload, parseContentBlocks, CreateNewsValidationRules, validate, newsController.createPost.bind(newsController));
49
65
 
@@ -9,10 +9,8 @@ class NewsService {
9
9
 
10
10
  async getTrendingNews() {
11
11
  try {
12
- // 1. Tentukan batas waktu (24 jam yang lalu dari sekarang)
13
12
  const last24Hours = new Date(new Date() - 24 * 60 * 60 * 1000);
14
13
 
15
- // 2. Query untuk menghitung views per berita
16
14
  const trending = await this.News.findAll({
17
15
  attributes: [
18
16
  'id',
@@ -22,24 +20,23 @@ class NewsService {
22
20
  'imagePath',
23
21
  'authorName',
24
22
  'createdAt',
25
- // Membuat kolom virtual 'totalViews' dari hasil hitung (COUNT)
26
23
  [fn('COUNT', col('visits.id')), 'totalViews']
27
24
  ],
28
25
  include: [{
29
26
  model: this.VisitorLog,
30
- as: 'visits', // SESUAI dengan alias relasi Anda
31
- attributes: [], // Kita tidak butuh kolom detail dari VisitorLog
27
+ as: 'visits',
28
+ attributes: [],
32
29
  where: {
33
30
  visitedAt: {
34
- [Op.gt]: last24Hours // Hanya log dalam 24 jam terakhir
31
+ [Op.gt]: last24Hours
35
32
  }
36
33
  },
37
- required: true // Menggunakan INNER JOIN agar hanya berita yang ada view-nya yang muncul
34
+ required: true
38
35
  }],
39
- group: ['News.id'], // Kelompokkan berdasarkan ID berita
40
- order: [[fn('COUNT', col('visits.id')), 'DESC']], // Urutkan terbanyak ke terendah
41
- limit: 10, // Ambil 10 teratas
42
- subQuery: false // WAJIB false agar LIMIT dan GROUP BY bekerja benar dengan JOIN
36
+ group: ['News.id'],
37
+ order: [[fn('COUNT', col('visits.id')), 'DESC']],
38
+ limit: 10,
39
+ subQuery: false
43
40
  });
44
41
 
45
42
  return trending;
@@ -12,13 +12,13 @@
12
12
  <!-- Sidebar -->
13
13
  <nav class="sidebar">
14
14
  <div class="logo">
15
- <a href="index.html" style="display: flex; justify-content: center;"><img src="../../img/logo.png"
15
+ <a href="./dashboard" style="display: flex; justify-content: center;"><img src="/berita/img/logo.png"
16
16
  alt="Logo DeNews" /></a>
17
17
  </div>
18
18
 
19
19
  <ul class="nav-menu">
20
20
  <li class="nav-item">
21
- <a href="index.html" class="nav-link">
21
+ <a href="./dashboard" class="nav-link">
22
22
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
23
23
  <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
24
24
  <polyline points="9 22 9 12 15 12 15 22"></polyline>
@@ -73,6 +73,9 @@
73
73
  <option value="Hiburan">Hiburan</option>
74
74
  <option value="Olahraga">Olahraga</option>
75
75
  <option value="Nasional">Nasional</option>
76
+ <option value="Teknologi">Teknologi</option>
77
+ <option value="Game">Game</option>
78
+ <option value="Kesehatan">Kesehatan</option>
76
79
  </select>
77
80
  </div>
78
81
 
@@ -279,29 +282,22 @@
279
282
 
280
283
  console.log(contentBlocksData);
281
284
 
282
- console.log("step 6");
283
285
 
284
286
  // Kirim metadata blok sebagai string JSON (Standar multipart/form-data untuk array of objects)
285
287
  formData.append('contentBlocks', JSON.stringify(contentBlocksData));
286
-
287
- console.log("step 7");
288
-
289
288
  try {
290
- const response = await fetch('http://localhost:3000/berita/cms-admin/create', {
289
+ const response = await fetch('<%= apiBaseUrl %>', {
291
290
  method: 'POST',
292
291
  body: formData
293
292
  // Jangan set Header Content-Type, browser akan otomatis mengurusnya untuk FormData
294
293
  });
295
294
 
296
- console.log("step 8");
297
-
298
295
  const result = await response.json();
299
296
 
300
- console.log("step 9");
301
297
 
302
298
  if (response.ok && result.success) {
303
299
  alert('Berita berhasil disimpan!');
304
- window.location.href = '/berita/cms-admin/list';
300
+ window.location.href = '<%= nextUrl %>';
305
301
  } else {
306
302
  alert('Gagal membuat berita: ' + (result.error || 'Terjadi kesalahan server'));
307
303
  }
@@ -89,7 +89,7 @@
89
89
  %>
90
90
  <div class="bar-wrapper">
91
91
  <div class="bar" data-height="<%= Math.round(heightPercent) %>"
92
- style="height: <%= Math.round(heightPercent) %>%;">
92
+ style="height: <%= Math.round(heightPercent) %>%">
93
93
  </div>
94
94
  <span class="bar-label">
95
95
  <%= monthName %>
@@ -12,13 +12,13 @@
12
12
  <!-- Sidebar -->
13
13
  <nav class="sidebar">
14
14
  <div class="logo">
15
- <a href="index.html" style="display: flex; justify-content: center;"><img src="../../img/logo.png"
15
+ <a href="index.html" style="display: flex; justify-content: center;"><img src="/berita/img/logo.png"
16
16
  alt="Logo DeNews" /></a>
17
17
  </div>
18
18
 
19
19
  <ul class="nav-menu">
20
20
  <li class="nav-item">
21
- <a href="index.html" class="nav-link">
21
+ <a href="./dashboard" class="nav-link">
22
22
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
23
23
  <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
24
24
  <polyline points="9 22 9 12 15 12 15 22"></polyline>
@@ -329,14 +329,10 @@
329
329
  });
330
330
 
331
331
  console.log(contentBlocksData);
332
-
333
- console.log("step 6");
334
332
 
335
333
  // Kirim metadata blok sebagai string JSON (Standar multipart/form-data untuk array of objects)
336
334
  formData.append('contentBlocks', JSON.stringify(contentBlocksData));
337
335
 
338
- console.log("step 7");
339
-
340
336
  try {
341
337
  let url = 'http://localhost:3000/berita/cms-admin/update/' + '<%= data.id %>';
342
338
  const response = await fetch( url, {
@@ -345,12 +341,8 @@
345
341
  // Jangan set Header Content-Type, browser akan otomatis mengurusnya untuk FormData
346
342
  });
347
343
 
348
- console.log("step 8");
349
-
350
344
  const result = await response.json();
351
345
 
352
- console.log("step 9");
353
-
354
346
  if (response.ok && result.success) {
355
347
  alert('Berita berhasil disimpan!');
356
348
  window.location.href = '/berita/cms-admin/list';