This commit is contained in:
@@ -1,50 +0,0 @@
|
|||||||
const {sequelize} = require('./config/database');
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
await sequelize.authenticate();
|
|
||||||
console.log('✅ Database connected');
|
|
||||||
|
|
||||||
// Check current columns
|
|
||||||
const [cols] = await sequelize.query('DESCRIBE context');
|
|
||||||
console.log('\n📋 Current columns:');
|
|
||||||
cols.forEach(c => console.log(` - ${c.Field}`));
|
|
||||||
|
|
||||||
const columnNames = cols.map(c => c.Field);
|
|
||||||
|
|
||||||
// Add knowledge column if not exists
|
|
||||||
if (!columnNames.includes('knowledge')) {
|
|
||||||
await sequelize.query(`
|
|
||||||
ALTER TABLE context
|
|
||||||
ADD COLUMN knowledge TEXT NULL
|
|
||||||
COMMENT 'Additional knowledge or information'
|
|
||||||
`);
|
|
||||||
console.log('\n✅ Added knowledge column');
|
|
||||||
} else {
|
|
||||||
console.log('\n✅ knowledge column already exists');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add status column if not exists
|
|
||||||
if (!columnNames.includes('status')) {
|
|
||||||
await sequelize.query(`
|
|
||||||
ALTER TABLE context
|
|
||||||
ADD COLUMN status INT DEFAULT 0
|
|
||||||
COMMENT '0: Draft, 1: Enriched, 2: Prompt_Ready, 3: Generating, 4: Image_Ready, 5: Approved'
|
|
||||||
`);
|
|
||||||
console.log('✅ Added status column');
|
|
||||||
} else {
|
|
||||||
console.log('✅ status column already exists');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show final structure
|
|
||||||
const [finalCols] = await sequelize.query('DESCRIBE context');
|
|
||||||
console.log('\n📊 Final Context table structure:');
|
|
||||||
finalCols.forEach((c, i) => console.log(` ${i+1}. ${c.Field} (${c.Type})`));
|
|
||||||
|
|
||||||
process.exit(0);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Error:', error.message);
|
|
||||||
console.error(error.stack);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
2
app.js
2
app.js
@@ -20,6 +20,7 @@ const authRoutes = require('./routes/authRoutes');
|
|||||||
const schoolRoutes = require('./routes/schoolRoutes');
|
const schoolRoutes = require('./routes/schoolRoutes');
|
||||||
const classRoutes = require('./routes/classRoutes');
|
const classRoutes = require('./routes/classRoutes');
|
||||||
const academicYearRoutes = require('./routes/academicYearRoutes');
|
const academicYearRoutes = require('./routes/academicYearRoutes');
|
||||||
|
const categoryRoutes = require('./routes/categoryRoutes');
|
||||||
const subjectRoutes = require('./routes/subjectRoutes');
|
const subjectRoutes = require('./routes/subjectRoutes');
|
||||||
const userRoutes = require('./routes/userRoutes');
|
const userRoutes = require('./routes/userRoutes');
|
||||||
const studentRoutes = require('./routes/studentRoutes');
|
const studentRoutes = require('./routes/studentRoutes');
|
||||||
@@ -217,6 +218,7 @@ app.use('/api/auth', authRoutes);
|
|||||||
app.use('/api/schools', schoolRoutes);
|
app.use('/api/schools', schoolRoutes);
|
||||||
app.use('/api/classes', classRoutes);
|
app.use('/api/classes', classRoutes);
|
||||||
app.use('/api/academic-years', academicYearRoutes);
|
app.use('/api/academic-years', academicYearRoutes);
|
||||||
|
app.use('/api/categories', categoryRoutes);
|
||||||
app.use('/api/subjects', subjectRoutes);
|
app.use('/api/subjects', subjectRoutes);
|
||||||
app.use('/api/users', userRoutes);
|
app.use('/api/users', userRoutes);
|
||||||
app.use('/api/students', studentRoutes);
|
app.use('/api/students', studentRoutes);
|
||||||
|
|||||||
354
controllers/categoryController.js
Normal file
354
controllers/categoryController.js
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
const { Categories, Subject } = require('../models');
|
||||||
|
const { cacheUtils } = require('../config/redis');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Categories Controller - Quản lý danh mục
|
||||||
|
*/
|
||||||
|
class CategoryController {
|
||||||
|
/**
|
||||||
|
* Get all categories with pagination and caching
|
||||||
|
*/
|
||||||
|
async getAllCategories(req, res, next) {
|
||||||
|
try {
|
||||||
|
const { page = 1, limit = 50, is_active } = req.query;
|
||||||
|
const offset = (page - 1) * limit;
|
||||||
|
|
||||||
|
// Generate cache key
|
||||||
|
const cacheKey = `categories:list:${page}:${limit}:${is_active || 'all'}`;
|
||||||
|
|
||||||
|
// Try to get from cache first
|
||||||
|
const cached = await cacheUtils.get(cacheKey);
|
||||||
|
if (cached) {
|
||||||
|
return res.json({
|
||||||
|
success: true,
|
||||||
|
data: cached,
|
||||||
|
cached: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build query conditions
|
||||||
|
const where = {};
|
||||||
|
if (is_active !== undefined) where.is_active = is_active === 'true';
|
||||||
|
|
||||||
|
// Query from database (through ProxySQL)
|
||||||
|
const { count, rows } = await Categories.findAndCountAll({
|
||||||
|
where,
|
||||||
|
limit: parseInt(limit),
|
||||||
|
offset: parseInt(offset),
|
||||||
|
order: [['category_code', 'ASC']],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
categories: rows,
|
||||||
|
pagination: {
|
||||||
|
total: count,
|
||||||
|
page: parseInt(page),
|
||||||
|
limit: parseInt(limit),
|
||||||
|
totalPages: Math.ceil(count / limit),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
await cacheUtils.set(cacheKey, result, 7200); // Cache for 2 hours
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: result,
|
||||||
|
cached: false,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get category by ID with caching
|
||||||
|
*/
|
||||||
|
async getCategoryById(req, res, next) {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const cacheKey = `category:${id}`;
|
||||||
|
|
||||||
|
// Try cache first
|
||||||
|
const cached = await cacheUtils.get(cacheKey);
|
||||||
|
if (cached) {
|
||||||
|
return res.json({
|
||||||
|
success: true,
|
||||||
|
data: cached,
|
||||||
|
cached: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query from database
|
||||||
|
const category = await Categories.findByPk(id);
|
||||||
|
|
||||||
|
if (!category) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Category not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
await cacheUtils.set(cacheKey, category, 7200); // Cache for 2 hours
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: category,
|
||||||
|
cached: false,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get category by code with caching
|
||||||
|
*/
|
||||||
|
async getCategoryByCode(req, res, next) {
|
||||||
|
try {
|
||||||
|
const { code } = req.params;
|
||||||
|
const cacheKey = `category:code:${code}`;
|
||||||
|
|
||||||
|
// Try cache first
|
||||||
|
const cached = await cacheUtils.get(cacheKey);
|
||||||
|
if (cached) {
|
||||||
|
return res.json({
|
||||||
|
success: true,
|
||||||
|
data: cached,
|
||||||
|
cached: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query from database
|
||||||
|
const category = await Categories.findOne({
|
||||||
|
where: { category_code: code },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!category) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Category not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
await cacheUtils.set(cacheKey, category, 7200); // Cache for 2 hours
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: category,
|
||||||
|
cached: false,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new category
|
||||||
|
*/
|
||||||
|
async createCategory(req, res, next) {
|
||||||
|
try {
|
||||||
|
const categoryData = req.body;
|
||||||
|
|
||||||
|
const category = await Categories.create(categoryData);
|
||||||
|
|
||||||
|
await cacheUtils.deletePattern('categories:list:*');
|
||||||
|
|
||||||
|
res.status(201).json({
|
||||||
|
success: true,
|
||||||
|
message: 'Category created successfully',
|
||||||
|
data: category,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update category
|
||||||
|
*/
|
||||||
|
async updateCategory(req, res, next) {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const updates = req.body;
|
||||||
|
|
||||||
|
const category = await Categories.findByPk(id);
|
||||||
|
|
||||||
|
if (!category) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Category not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await category.update(updates);
|
||||||
|
|
||||||
|
await cacheUtils.delete(`category:${id}`);
|
||||||
|
await cacheUtils.deletePattern('categories:list:*');
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Category updated successfully',
|
||||||
|
data: category,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete category
|
||||||
|
*/
|
||||||
|
async deleteCategory(req, res, next) {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
const category = await Categories.findByPk(id);
|
||||||
|
|
||||||
|
if (!category) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Category not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await category.destroy();
|
||||||
|
|
||||||
|
await cacheUtils.delete(`category:${id}`);
|
||||||
|
await cacheUtils.deletePattern('categories:list:*');
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Category deleted successfully',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get active categories (frequently used)
|
||||||
|
*/
|
||||||
|
async getActiveCategories(req, res, next) {
|
||||||
|
try {
|
||||||
|
const cacheKey = 'categories:active';
|
||||||
|
|
||||||
|
// Try cache first
|
||||||
|
const cached = await cacheUtils.get(cacheKey);
|
||||||
|
if (cached) {
|
||||||
|
return res.json({
|
||||||
|
success: true,
|
||||||
|
data: cached,
|
||||||
|
cached: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query from database
|
||||||
|
const categories = await Categories.findAll({
|
||||||
|
where: { is_active: true },
|
||||||
|
order: [['category_code', 'ASC']],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cache for 4 hours (this data changes infrequently)
|
||||||
|
await cacheUtils.set(cacheKey, categories, 14400);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: categories,
|
||||||
|
cached: false,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get category datatypes
|
||||||
|
*/
|
||||||
|
async getCategoryDatatypes(req, res, next) {
|
||||||
|
try {
|
||||||
|
const datatypes = Categories.rawAttributes;
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: datatypes,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get subjects by category ID
|
||||||
|
*/
|
||||||
|
async getSubjectsByCategory(req, res, next) {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const { page = 1, limit = 50, is_active } = req.query;
|
||||||
|
const offset = (page - 1) * limit;
|
||||||
|
|
||||||
|
// Generate cache key
|
||||||
|
const cacheKey = `category:${id}:subjects:${page}:${limit}:${is_active || 'all'}`;
|
||||||
|
|
||||||
|
// Try cache first
|
||||||
|
const cached = await cacheUtils.get(cacheKey);
|
||||||
|
if (cached) {
|
||||||
|
return res.json({
|
||||||
|
success: true,
|
||||||
|
data: cached,
|
||||||
|
cached: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if category exists
|
||||||
|
const category = await Categories.findByPk(id);
|
||||||
|
if (!category) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Category not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build query conditions
|
||||||
|
const where = { category_id: id };
|
||||||
|
if (is_active !== undefined) where.is_active = is_active === 'true';
|
||||||
|
|
||||||
|
// Query subjects
|
||||||
|
const { count, rows } = await Subject.findAndCountAll({
|
||||||
|
where,
|
||||||
|
limit: parseInt(limit),
|
||||||
|
offset: parseInt(offset),
|
||||||
|
order: [['subject_code', 'ASC']],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
category: {
|
||||||
|
id: category.id,
|
||||||
|
category_code: category.category_code,
|
||||||
|
category_name: category.category_name,
|
||||||
|
},
|
||||||
|
subjects: rows,
|
||||||
|
pagination: {
|
||||||
|
total: count,
|
||||||
|
page: parseInt(page),
|
||||||
|
limit: parseInt(limit),
|
||||||
|
totalPages: Math.ceil(count / limit),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
await cacheUtils.set(cacheKey, result, 3600); // Cache for 1 hour
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: result,
|
||||||
|
cached: false,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new CategoryController();
|
||||||
30
models/Categories.js
Normal file
30
models/Categories.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
const { DataTypes } = require('sequelize');
|
||||||
|
const { sequelize } = require('../config/database');
|
||||||
|
|
||||||
|
const Categories = sequelize.define('Categories', {
|
||||||
|
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
|
||||||
|
category_code: { type: DataTypes.STRING(20), unique: true, allowNull: false },
|
||||||
|
category_name: { type: DataTypes.STRING(100), allowNull: false },
|
||||||
|
category_thumbnail: { type: DataTypes.STRING(255) },
|
||||||
|
category_name_en: { type: DataTypes.STRING(100) },
|
||||||
|
description: { type: DataTypes.TEXT },
|
||||||
|
is_active: { type: DataTypes.BOOLEAN, defaultValue: true },
|
||||||
|
is_premium: { type: DataTypes.BOOLEAN, defaultValue: false, comment: 'Nội dung premium' },
|
||||||
|
is_training: { type: DataTypes.BOOLEAN, defaultValue: false, comment: 'Nội dung đào tạo nhân sự' },
|
||||||
|
is_public: { type: DataTypes.BOOLEAN, defaultValue: false, comment: 'Nội dung tự học công khai' },
|
||||||
|
required_role: { type: DataTypes.STRING(50), comment: 'Role yêu cầu' },
|
||||||
|
min_subscription_tier: { type: DataTypes.STRING(50), comment: 'Gói tối thiểu: basic, premium, vip' },
|
||||||
|
created_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
|
||||||
|
updated_at: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
|
||||||
|
}, {
|
||||||
|
tableName: 'Categories',
|
||||||
|
timestamps: true,
|
||||||
|
underscored: true,
|
||||||
|
indexes: [
|
||||||
|
{ fields: ['is_premium'] },
|
||||||
|
{ fields: ['is_training'] },
|
||||||
|
{ fields: ['is_public'] },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Categories;
|
||||||
@@ -3,6 +3,7 @@ const { sequelize } = require('../config/database');
|
|||||||
|
|
||||||
const Subject = sequelize.define('subjects', {
|
const Subject = sequelize.define('subjects', {
|
||||||
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
|
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
|
||||||
|
category_id: { type: DataTypes.UUID, allowNull: false, comment: 'Danh mục chủ đề' },
|
||||||
subject_code: { type: DataTypes.STRING(20), unique: true, allowNull: false },
|
subject_code: { type: DataTypes.STRING(20), unique: true, allowNull: false },
|
||||||
subject_name: { type: DataTypes.STRING(100), allowNull: false },
|
subject_name: { type: DataTypes.STRING(100), allowNull: false },
|
||||||
subject_name_en: { type: DataTypes.STRING(100) },
|
subject_name_en: { type: DataTypes.STRING(100) },
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const StaffContract = require('./StaffContract');
|
|||||||
|
|
||||||
// Group 3: Academic Structure
|
// Group 3: Academic Structure
|
||||||
const AcademicYear = require('./AcademicYear');
|
const AcademicYear = require('./AcademicYear');
|
||||||
|
const Categories = require('./Categories');
|
||||||
const Subject = require('./Subject');
|
const Subject = require('./Subject');
|
||||||
const Class = require('./Class');
|
const Class = require('./Class');
|
||||||
const ClassSchedule = require('./ClassSchedule');
|
const ClassSchedule = require('./ClassSchedule');
|
||||||
@@ -149,6 +150,10 @@ const setupRelationships = () => {
|
|||||||
ClassSchedule.belongsTo(TeacherDetail, { foreignKey: 'teacher_id', as: 'teacher' });
|
ClassSchedule.belongsTo(TeacherDetail, { foreignKey: 'teacher_id', as: 'teacher' });
|
||||||
|
|
||||||
// Learning Content relationships (NEW)
|
// Learning Content relationships (NEW)
|
||||||
|
// Categories -> Subject (1:N)
|
||||||
|
Categories.hasMany(Subject, { foreignKey: 'category_id', as: 'subjects' });
|
||||||
|
Subject.belongsTo(Categories, { foreignKey: 'category_id', as: 'category' });
|
||||||
|
|
||||||
// Subject -> Chapter (1:N)
|
// Subject -> Chapter (1:N)
|
||||||
Subject.hasMany(Chapter, { foreignKey: 'subject_id', as: 'chapters' });
|
Subject.hasMany(Chapter, { foreignKey: 'subject_id', as: 'chapters' });
|
||||||
Chapter.belongsTo(Subject, { foreignKey: 'subject_id', as: 'subject' });
|
Chapter.belongsTo(Subject, { foreignKey: 'subject_id', as: 'subject' });
|
||||||
@@ -277,6 +282,7 @@ module.exports = {
|
|||||||
|
|
||||||
// Group 3: Academic Structure
|
// Group 3: Academic Structure
|
||||||
AcademicYear,
|
AcademicYear,
|
||||||
|
Categories,
|
||||||
Subject,
|
Subject,
|
||||||
Class,
|
Class,
|
||||||
ClassSchedule,
|
ClassSchedule,
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
@echo off
|
|
||||||
REM Windows batch script to fix bcrypt on remote server
|
|
||||||
REM Usage: quick-fix-bcrypt.bat
|
|
||||||
|
|
||||||
set SERVER=root@senaai.tech
|
|
||||||
set PROJECT_PATH=/var/www/services/sena_db_api
|
|
||||||
|
|
||||||
echo ================================================================
|
|
||||||
echo Quick Fix Bcrypt Script
|
|
||||||
echo Server: %SERVER%
|
|
||||||
echo ================================================================
|
|
||||||
echo.
|
|
||||||
|
|
||||||
echo Connecting to server and fixing bcrypt...
|
|
||||||
echo.
|
|
||||||
|
|
||||||
ssh %SERVER% "cd %PROJECT_PATH% && npm rebuild bcrypt --build-from-source && npm rebuild && mkdir -p logs && echo. && echo [SUCCESS] Bcrypt rebuilt! && echo. && pm2 restart all 2>nul || pm2 start start.json && echo. && pm2 list"
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ================================================================
|
|
||||||
echo Done! Check the output above.
|
|
||||||
echo ================================================================
|
|
||||||
pause
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Script tự động rebuild bcrypt và restart PM2
|
|
||||||
# Usage: ./quick-fix-bcrypt.sh
|
|
||||||
|
|
||||||
SERVER="root@senaai.tech"
|
|
||||||
PROJECT_PATH="/var/www/services/sena_db_api"
|
|
||||||
|
|
||||||
echo "🔧 Quick fix bcrypt on $SERVER..."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
ssh $SERVER << ENDSSH
|
|
||||||
cd $PROJECT_PATH && \
|
|
||||||
echo "🔨 Rebuilding bcrypt..." && \
|
|
||||||
npm rebuild bcrypt --build-from-source && \
|
|
||||||
npm rebuild && \
|
|
||||||
mkdir -p logs && \
|
|
||||||
echo "" && \
|
|
||||||
echo "✅ Bcrypt rebuilt successfully!" && \
|
|
||||||
echo "" && \
|
|
||||||
echo "🔄 Restarting PM2..." && \
|
|
||||||
pm2 restart all 2>/dev/null || pm2 start start.json && \
|
|
||||||
echo "" && \
|
|
||||||
echo "📊 PM2 Status:" && \
|
|
||||||
pm2 list && \
|
|
||||||
echo "" && \
|
|
||||||
echo "✅ All done!"
|
|
||||||
ENDSSH
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
const {sequelize} = require('./config/database');
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
await sequelize.authenticate();
|
|
||||||
console.log('✅ Database connected');
|
|
||||||
|
|
||||||
// Rename promptForImage to img_prompt
|
|
||||||
await sequelize.query(`
|
|
||||||
ALTER TABLE context
|
|
||||||
CHANGE COLUMN promptForImage img_prompt JSON
|
|
||||||
COMMENT 'Prompt configuration object'
|
|
||||||
`);
|
|
||||||
console.log('✅ Renamed promptForImage to img_prompt');
|
|
||||||
|
|
||||||
// Show final structure
|
|
||||||
const [cols] = await sequelize.query('DESCRIBE context');
|
|
||||||
console.log('\n📊 Final Context table structure:');
|
|
||||||
cols.forEach((c, i) => console.log(` ${i+1}. ${c.Field} (${c.Type})`));
|
|
||||||
|
|
||||||
process.exit(0);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Error:', error.message);
|
|
||||||
console.error(error.stack);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
36
routes/categoryRoutes.js
Normal file
36
routes/categoryRoutes.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const categoryController = require('../controllers/categoryController');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Category Routes
|
||||||
|
*/
|
||||||
|
|
||||||
|
// GET /api/categories - Get all categories with pagination
|
||||||
|
router.get('/', categoryController.getAllCategories);
|
||||||
|
|
||||||
|
// GET /api/categories/active - Get all active categories
|
||||||
|
router.get('/active', categoryController.getActiveCategories);
|
||||||
|
|
||||||
|
// GET /api/categories/datatypes/schema - Get category datatypes
|
||||||
|
router.get('/datatypes/schema', categoryController.getCategoryDatatypes);
|
||||||
|
|
||||||
|
// GET /api/categories/code/:code - Get category by code
|
||||||
|
router.get('/code/:code', categoryController.getCategoryByCode);
|
||||||
|
|
||||||
|
// GET /api/categories/:id - Get category by ID
|
||||||
|
router.get('/:id', categoryController.getCategoryById);
|
||||||
|
|
||||||
|
// GET /api/categories/:id/subjects - Get subjects by category
|
||||||
|
router.get('/:id/subjects', categoryController.getSubjectsByCategory);
|
||||||
|
|
||||||
|
// POST /api/categories - Create new category
|
||||||
|
router.post('/', categoryController.createCategory);
|
||||||
|
|
||||||
|
// PUT /api/categories/:id - Update category
|
||||||
|
router.put('/:id', categoryController.updateCategory);
|
||||||
|
|
||||||
|
// DELETE /api/categories/:id - Delete category
|
||||||
|
router.delete('/:id', categoryController.deleteCategory);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
Reference in New Issue
Block a user