Merge branch 'main' of https://git.profit-planet.partners/DK404/CentralBackend
This commit is contained in:
commit
b73f8b9b4b
241
controller/news/NewsController.js
Normal file
241
controller/news/NewsController.js
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
const multer = require('multer')
|
||||||
|
const { S3Client, PutObjectCommand, DeleteObjectCommand } = require('@aws-sdk/client-s3')
|
||||||
|
const NewsService = require('../../services/news/NewsService')
|
||||||
|
const { logger } = require('../../middleware/logger')
|
||||||
|
|
||||||
|
const PREFIX = 'news/'
|
||||||
|
|
||||||
|
function buildImageUrlFromKey(key) {
|
||||||
|
if (!key) return ''
|
||||||
|
const endpoint = process.env.EXOSCALE_ENDPOINT || ''
|
||||||
|
const bucket = process.env.EXOSCALE_BUCKET || ''
|
||||||
|
if (endpoint.startsWith('http')) {
|
||||||
|
return `${endpoint.replace(/\/$/, '')}/${bucket}/${key}`
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.list = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const rows = await NewsService.list()
|
||||||
|
const data = (rows || []).map(r => ({
|
||||||
|
id: r.id,
|
||||||
|
title: r.title,
|
||||||
|
summary: r.summary,
|
||||||
|
slug: r.slug,
|
||||||
|
category: r.category,
|
||||||
|
is_active: !!r.is_active,
|
||||||
|
published_at: r.published_at,
|
||||||
|
imageUrl: r.object_storage_id ? buildImageUrlFromKey(r.object_storage_id) : ''
|
||||||
|
}))
|
||||||
|
res.json({ success: true, data })
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('[NewsController.list] error', { msg: e.message })
|
||||||
|
res.status(500).json({ error: 'Failed to load news' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.listActive = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const rows = await NewsService.list()
|
||||||
|
const data = (rows || [])
|
||||||
|
.filter(r => r.is_active)
|
||||||
|
.map(r => ({
|
||||||
|
id: r.id,
|
||||||
|
title: r.title,
|
||||||
|
summary: r.summary,
|
||||||
|
slug: r.slug,
|
||||||
|
category: r.category,
|
||||||
|
published_at: r.published_at,
|
||||||
|
imageUrl: r.object_storage_id ? buildImageUrlFromKey(r.object_storage_id) : ''
|
||||||
|
}))
|
||||||
|
res.json({ success: true, data })
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('[NewsController.listActive] error', { msg: e.message })
|
||||||
|
res.status(500).json({ error: 'Failed to load news' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.create = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { title, summary, content, slug, category, isActive, publishedAt } = req.body
|
||||||
|
if (!title || !slug) {
|
||||||
|
return res.status(400).json({ success: false, error: 'Title and slug are required' })
|
||||||
|
}
|
||||||
|
let objectKey = null
|
||||||
|
let originalFilename = null
|
||||||
|
|
||||||
|
if (req.file) {
|
||||||
|
const allowedMime = ['image/jpeg','image/png','image/webp','image/svg+xml']
|
||||||
|
if (!allowedMime.includes(req.file.mimetype)) {
|
||||||
|
return res.status(400).json({ success: false, error: 'Invalid image type. Allowed: JPG, PNG, WebP, SVG' })
|
||||||
|
}
|
||||||
|
if (req.file.size > 5 * 1024 * 1024) {
|
||||||
|
return res.status(400).json({ success: false, error: 'Image exceeds 5MB limit' })
|
||||||
|
}
|
||||||
|
const s3 = new S3Client({
|
||||||
|
region: process.env.EXOSCALE_REGION,
|
||||||
|
endpoint: process.env.EXOSCALE_ENDPOINT,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: process.env.EXOSCALE_ACCESS_KEY,
|
||||||
|
secretAccessKey: process.env.EXOSCALE_SECRET_KEY,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
originalFilename = req.file.originalname
|
||||||
|
const key = `${PREFIX}${Date.now()}_${originalFilename.replace(/\s+/g, '_')}`
|
||||||
|
await s3.send(new PutObjectCommand({
|
||||||
|
Bucket: process.env.EXOSCALE_BUCKET,
|
||||||
|
Key: key,
|
||||||
|
Body: req.file.buffer,
|
||||||
|
ContentType: req.file.mimetype,
|
||||||
|
ACL: 'public-read'
|
||||||
|
}))
|
||||||
|
objectKey = key
|
||||||
|
logger.info('[NewsController.create] uploaded image', { key })
|
||||||
|
}
|
||||||
|
|
||||||
|
let id
|
||||||
|
try {
|
||||||
|
id = await NewsService.create({
|
||||||
|
title,
|
||||||
|
summary,
|
||||||
|
content,
|
||||||
|
slug,
|
||||||
|
category,
|
||||||
|
object_storage_id: objectKey,
|
||||||
|
original_filename: originalFilename,
|
||||||
|
is_active: isActive ? 1 : 0,
|
||||||
|
published_at: publishedAt || null,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
const msg = err && err.message ? err.message : String(err)
|
||||||
|
// Handle duplicate slug gracefully
|
||||||
|
if (msg.includes('ER_DUP_ENTRY') || msg.toLowerCase().includes('duplicate')) {
|
||||||
|
return res.status(409).json({ success: false, error: 'Slug already exists' })
|
||||||
|
}
|
||||||
|
logger.error('[NewsController.create] db error', { msg })
|
||||||
|
return res.status(500).json({ success: false, error: 'Database error while creating news' })
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ success: true, data: { id } })
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('[NewsController.create] error', { msg: e.message })
|
||||||
|
res.status(500).json({ success: false, error: e.message || 'Failed to create news' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.update = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params
|
||||||
|
const { title, summary, content, slug, category, isActive, publishedAt, removeImage } = req.body
|
||||||
|
const existing = await NewsService.get(id)
|
||||||
|
if (!existing) return res.status(404).json({ error: 'Not found' })
|
||||||
|
|
||||||
|
let objectKey = existing.object_storage_id
|
||||||
|
let originalFilename = existing.original_filename
|
||||||
|
|
||||||
|
if (removeImage && objectKey) {
|
||||||
|
try {
|
||||||
|
const s3 = new S3Client({
|
||||||
|
region: process.env.EXOSCALE_REGION,
|
||||||
|
endpoint: process.env.EXOSCALE_ENDPOINT,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: process.env.EXOSCALE_ACCESS_KEY,
|
||||||
|
secretAccessKey: process.env.EXOSCALE_SECRET_KEY,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await s3.send(new DeleteObjectCommand({ Bucket: process.env.EXOSCALE_BUCKET, Key: objectKey }))
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('[NewsController.update] delete image error', { msg: err.message })
|
||||||
|
}
|
||||||
|
objectKey = null
|
||||||
|
originalFilename = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.file) {
|
||||||
|
const allowedMime = ['image/jpeg','image/png','image/webp','image/svg+xml']
|
||||||
|
if (!allowedMime.includes(req.file.mimetype)) {
|
||||||
|
return res.status(400).json({ success: false, error: 'Invalid image type. Allowed: JPG, PNG, WebP, SVG' })
|
||||||
|
}
|
||||||
|
if (req.file.size > 5 * 1024 * 1024) {
|
||||||
|
return res.status(400).json({ success: false, error: 'Image exceeds 5MB limit' })
|
||||||
|
}
|
||||||
|
const s3 = new S3Client({
|
||||||
|
region: process.env.EXOSCALE_REGION,
|
||||||
|
endpoint: process.env.EXOSCALE_ENDPOINT,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: process.env.EXOSCALE_ACCESS_KEY,
|
||||||
|
secretAccessKey: process.env.EXOSCALE_SECRET_KEY,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const newKey = `${PREFIX}${Date.now()}_${req.file.originalname.replace(/\s+/g, '_')}`
|
||||||
|
await s3.send(new PutObjectCommand({
|
||||||
|
Bucket: process.env.EXOSCALE_BUCKET,
|
||||||
|
Key: newKey,
|
||||||
|
Body: req.file.buffer,
|
||||||
|
ContentType: req.file.mimetype,
|
||||||
|
ACL: 'public-read'
|
||||||
|
}))
|
||||||
|
objectKey = newKey
|
||||||
|
originalFilename = req.file.originalname
|
||||||
|
logger.info('[NewsController.update] uploaded new image', { key: newKey })
|
||||||
|
}
|
||||||
|
|
||||||
|
await NewsService.update(id, {
|
||||||
|
title,
|
||||||
|
summary,
|
||||||
|
content,
|
||||||
|
slug,
|
||||||
|
category,
|
||||||
|
object_storage_id: objectKey,
|
||||||
|
original_filename: originalFilename,
|
||||||
|
is_active: isActive !== undefined ? (isActive ? 1 : 0) : undefined,
|
||||||
|
published_at: publishedAt !== undefined ? (publishedAt || null) : undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
res.json({ success: true })
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('[NewsController.update] error', { msg: e.message })
|
||||||
|
res.status(500).json({ error: 'Failed to update news' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.updateStatus = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params
|
||||||
|
const { isActive } = req.body
|
||||||
|
await NewsService.update(id, { is_active: isActive ? 1 : 0 })
|
||||||
|
res.json({ success: true })
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('[NewsController.updateStatus] error', { msg: e.message })
|
||||||
|
res.status(500).json({ error: 'Failed to update status' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.delete = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params
|
||||||
|
const existing = await NewsService.get(id)
|
||||||
|
if (!existing) return res.status(404).json({ error: 'Not found' })
|
||||||
|
if (existing.object_storage_id) {
|
||||||
|
try {
|
||||||
|
const s3 = new S3Client({
|
||||||
|
region: process.env.EXOSCALE_REGION,
|
||||||
|
endpoint: process.env.EXOSCALE_ENDPOINT,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: process.env.EXOSCALE_ACCESS_KEY,
|
||||||
|
secretAccessKey: process.env.EXOSCALE_SECRET_KEY,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await s3.send(new DeleteObjectCommand({ Bucket: process.env.EXOSCALE_BUCKET, Key: existing.object_storage_id }))
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('[NewsController.delete] delete image error', { msg: err.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await NewsService.delete(id)
|
||||||
|
res.json({ success: true })
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('[NewsController.delete] error', { msg: e.message })
|
||||||
|
res.status(500).json({ error: 'Failed to delete news' })
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -436,6 +436,25 @@ async function createDatabase() {
|
|||||||
INDEX idx_created_by (created_by_user_id)
|
INDEX idx_created_by (created_by_user_id)
|
||||||
);
|
);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
// News table for News Manager
|
||||||
|
await connection.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS news (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
summary TEXT NULL,
|
||||||
|
content MEDIUMTEXT NULL,
|
||||||
|
slug VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
category VARCHAR(128) NULL,
|
||||||
|
object_storage_id VARCHAR(255) NULL,
|
||||||
|
original_filename VARCHAR(255) NULL,
|
||||||
|
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
published_at DATETIME NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
console.log('✅ News table created/verified');
|
||||||
console.log('✅ Referral tokens table created/verified');
|
console.log('✅ Referral tokens table created/verified');
|
||||||
|
|
||||||
// 13. referral_token_usage table: Tracks each use of a referral token
|
// 13. referral_token_usage table: Tracks each use of a referral token
|
||||||
|
|||||||
35
models/News.js
Normal file
35
models/News.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
class News {
|
||||||
|
constructor(row) {
|
||||||
|
this.id = row.id
|
||||||
|
this.title = row.title
|
||||||
|
this.summary = row.summary
|
||||||
|
this.content = row.content
|
||||||
|
this.slug = row.slug
|
||||||
|
this.category = row.category
|
||||||
|
this.object_storage_id = row.object_storage_id
|
||||||
|
this.original_filename = row.original_filename
|
||||||
|
this.is_active = !!row.is_active
|
||||||
|
this.published_at = row.published_at
|
||||||
|
this.created_at = row.created_at
|
||||||
|
this.updated_at = row.updated_at
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
title: this.title,
|
||||||
|
summary: this.summary,
|
||||||
|
content: this.content,
|
||||||
|
slug: this.slug,
|
||||||
|
category: this.category,
|
||||||
|
object_storage_id: this.object_storage_id,
|
||||||
|
original_filename: this.original_filename,
|
||||||
|
is_active: this.is_active,
|
||||||
|
published_at: this.published_at,
|
||||||
|
created_at: this.created_at,
|
||||||
|
updated_at: this.updated_at,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = News
|
||||||
@ -7,6 +7,7 @@ const DocumentTemplateController = require('../controller/documentTemplate/Docum
|
|||||||
const CompanyStampController = require('../controller/companyStamp/CompanyStampController');
|
const CompanyStampController = require('../controller/companyStamp/CompanyStampController');
|
||||||
const CoffeeController = require('../controller/admin/CoffeeController');
|
const CoffeeController = require('../controller/admin/CoffeeController');
|
||||||
const AffiliateController = require('../controller/affiliate/AffiliateController');
|
const AffiliateController = require('../controller/affiliate/AffiliateController');
|
||||||
|
const NewsController = require('../controller/news/NewsController');
|
||||||
|
|
||||||
// Helper middlewares for company-stamp
|
// Helper middlewares for company-stamp
|
||||||
function adminOnly(req, res, next) {
|
function adminOnly(req, res, next) {
|
||||||
@ -35,4 +36,7 @@ router.delete('/admin/coffee/:id', authMiddleware, adminOnly, CoffeeController.r
|
|||||||
// Admin: delete affiliate
|
// Admin: delete affiliate
|
||||||
router.delete('/admin/affiliates/:id', authMiddleware, adminOnly, AffiliateController.delete);
|
router.delete('/admin/affiliates/:id', authMiddleware, adminOnly, AffiliateController.delete);
|
||||||
|
|
||||||
|
// Admin: delete news
|
||||||
|
router.delete('/admin/news/:id', authMiddleware, adminOnly, NewsController.delete);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@ -20,6 +20,7 @@ const PoolController = require('../controller/pool/PoolController');
|
|||||||
const TaxController = require('../controller/tax/taxController');
|
const TaxController = require('../controller/tax/taxController');
|
||||||
const AffiliateController = require('../controller/affiliate/AffiliateController');
|
const AffiliateController = require('../controller/affiliate/AffiliateController');
|
||||||
const AbonemmentController = require('../controller/abonemments/AbonemmentController');
|
const AbonemmentController = require('../controller/abonemments/AbonemmentController');
|
||||||
|
const NewsController = require('../controller/news/NewsController');
|
||||||
|
|
||||||
// small helpers copied from original files
|
// small helpers copied from original files
|
||||||
function adminOnly(req, res, next) {
|
function adminOnly(req, res, next) {
|
||||||
@ -153,4 +154,9 @@ router.get('/abonements/mine', authMiddleware, AbonemmentController.getMine);
|
|||||||
router.get('/abonements/:id/history', authMiddleware, AbonemmentController.getHistory);
|
router.get('/abonements/:id/history', authMiddleware, AbonemmentController.getHistory);
|
||||||
router.get('/admin/abonements', authMiddleware, adminOnly, AbonemmentController.adminList);
|
router.get('/admin/abonements', authMiddleware, adminOnly, AbonemmentController.adminList);
|
||||||
|
|
||||||
|
// News Manager Routes
|
||||||
|
router.get('/admin/news', authMiddleware, adminOnly, NewsController.list);
|
||||||
|
router.get('/news/active', NewsController.listActive);
|
||||||
|
|
||||||
|
// export
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@ -10,6 +10,7 @@ const PersonalProfileController = require('../controller/profile/PersonalProfile
|
|||||||
const PoolController = require('../controller/pool/PoolController'); // <-- new
|
const PoolController = require('../controller/pool/PoolController'); // <-- new
|
||||||
const MatrixController = require('../controller/matrix/MatrixController'); // <-- new
|
const MatrixController = require('../controller/matrix/MatrixController'); // <-- new
|
||||||
const AffiliateController = require('../controller/affiliate/AffiliateController'); // <-- new
|
const AffiliateController = require('../controller/affiliate/AffiliateController'); // <-- new
|
||||||
|
const NewsController = require('../controller/news/NewsController');
|
||||||
|
|
||||||
const multer = require('multer');
|
const multer = require('multer');
|
||||||
const upload = multer({ storage: multer.memoryStorage() });
|
const upload = multer({ storage: multer.memoryStorage() });
|
||||||
@ -53,6 +54,10 @@ router.patch('/admin/affiliates/:id', authMiddleware, adminOnly, upload.single('
|
|||||||
// NEW: Update affiliate status only
|
// NEW: Update affiliate status only
|
||||||
router.patch('/admin/affiliates/:id/status', authMiddleware, adminOnly, AffiliateController.updateStatus);
|
router.patch('/admin/affiliates/:id/status', authMiddleware, adminOnly, AffiliateController.updateStatus);
|
||||||
|
|
||||||
|
// News Manager
|
||||||
|
router.patch('/admin/news/:id', authMiddleware, adminOnly, upload.single('image'), NewsController.update);
|
||||||
|
router.patch('/admin/news/:id/status', authMiddleware, adminOnly, NewsController.updateStatus);
|
||||||
|
|
||||||
// Personal profile (self-service) - no admin guard
|
// Personal profile (self-service) - no admin guard
|
||||||
router.patch('/profile/personal/basic', authMiddleware, PersonalProfileController.updateBasic);
|
router.patch('/profile/personal/basic', authMiddleware, PersonalProfileController.updateBasic);
|
||||||
router.patch('/profile/personal/bank', authMiddleware, PersonalProfileController.updateBank);
|
router.patch('/profile/personal/bank', authMiddleware, PersonalProfileController.updateBank);
|
||||||
|
|||||||
@ -26,6 +26,7 @@ const PoolController = require('../controller/pool/PoolController');
|
|||||||
const TaxController = require('../controller/tax/taxController');
|
const TaxController = require('../controller/tax/taxController');
|
||||||
const AffiliateController = require('../controller/affiliate/AffiliateController');
|
const AffiliateController = require('../controller/affiliate/AffiliateController');
|
||||||
const AbonemmentController = require('../controller/abonemments/AbonemmentController');
|
const AbonemmentController = require('../controller/abonemments/AbonemmentController');
|
||||||
|
const NewsController = require('../controller/news/NewsController');
|
||||||
|
|
||||||
const multer = require('multer');
|
const multer = require('multer');
|
||||||
const upload = multer({ storage: multer.memoryStorage() });
|
const upload = multer({ storage: multer.memoryStorage() });
|
||||||
@ -156,6 +157,8 @@ router.post('/admin/pools', authMiddleware, adminOnly, PoolController.create);
|
|||||||
router.post('/tax/vat-rates/import', authMiddleware, adminOnly, upload.single('file'), TaxController.importVatRatesCsv);
|
router.post('/tax/vat-rates/import', authMiddleware, adminOnly, upload.single('file'), TaxController.importVatRatesCsv);
|
||||||
// NEW: Admin create affiliate with logo upload
|
// NEW: Admin create affiliate with logo upload
|
||||||
router.post('/admin/affiliates', authMiddleware, adminOnly, upload.single('logo'), AffiliateController.create);
|
router.post('/admin/affiliates', authMiddleware, adminOnly, upload.single('logo'), AffiliateController.create);
|
||||||
|
// NEW: Admin create news with image upload
|
||||||
|
router.post('/admin/news', authMiddleware, adminOnly, upload.single('image'), NewsController.create);
|
||||||
|
|
||||||
// Abonement POSTs
|
// Abonement POSTs
|
||||||
router.post('/abonements/subscribe', authMiddleware, AbonemmentController.subscribe);
|
router.post('/abonements/subscribe', authMiddleware, AbonemmentController.subscribe);
|
||||||
|
|||||||
51
services/news/NewsService.js
Normal file
51
services/news/NewsService.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
const db = require('../../database/database')
|
||||||
|
|
||||||
|
exports.list = async () => {
|
||||||
|
const [rows] = await db.query('SELECT * FROM news ORDER BY published_at DESC, created_at DESC')
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.get = async (idOrSlug) => {
|
||||||
|
const [rows] = await db.query('SELECT * FROM news WHERE id = ? OR slug = ? LIMIT 1', [idOrSlug, idOrSlug])
|
||||||
|
return rows?.[0] || null
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.create = async (payload) => {
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
summary,
|
||||||
|
content,
|
||||||
|
slug,
|
||||||
|
category,
|
||||||
|
object_storage_id,
|
||||||
|
original_filename,
|
||||||
|
is_active = 1,
|
||||||
|
published_at = null,
|
||||||
|
} = payload
|
||||||
|
const [res] = await db.query(
|
||||||
|
'INSERT INTO news (title, summary, content, slug, category, object_storage_id, original_filename, is_active, published_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||||
|
[title, summary, content, slug, category, object_storage_id, original_filename, is_active, published_at]
|
||||||
|
)
|
||||||
|
return res.insertId
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.update = async (id, payload) => {
|
||||||
|
const fields = []
|
||||||
|
const values = []
|
||||||
|
const allowed = ['title','summary','content','slug','category','object_storage_id','original_filename','is_active','published_at']
|
||||||
|
for (const key of allowed) {
|
||||||
|
if (payload[key] !== undefined) {
|
||||||
|
fields.push(`${key} = ?`)
|
||||||
|
values.push(payload[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!fields.length) return 0
|
||||||
|
values.push(id)
|
||||||
|
const [res] = await db.query(`UPDATE news SET ${fields.join(', ')} WHERE id = ?`, values)
|
||||||
|
return res.affectedRows
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.delete = async (id) => {
|
||||||
|
const [res] = await db.query('DELETE FROM news WHERE id = ?', [id])
|
||||||
|
return res.affectedRows
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user