create-skweb 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/.turbo/turbo-build.log +17 -0
- package/.turbo/turbo-check.log +4 -0
- package/.turbo/turbo-lint.log +4 -0
- package/.turbo/turbo-test.log +65 -0
- package/CHANGELOG.md +7 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +199 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +115 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
- package/rollup.config.mjs +71 -0
- package/src/cli.ts +118 -0
- package/src/index.ts +122 -0
- package/templates/cjs/ecosystem.config.js +32 -0
- package/templates/cjs/eslint.config.js +25 -0
- package/templates/cjs/package.json +46 -0
- package/templates/cjs/src/app.js +91 -0
- package/templates/cjs/src/commands/migrate.js +35 -0
- package/templates/cjs/src/commands/seed.js +58 -0
- package/templates/cjs/src/controllers/hello.js +33 -0
- package/templates/cjs/src/controllers/user.js +101 -0
- package/templates/cjs/src/cronActions/cleanup.js +12 -0
- package/templates/cjs/src/cronWorker.js +85 -0
- package/templates/cjs/src/index.js +7 -0
- package/templates/cjs/src/models/SysUser.js +38 -0
- package/templates/cjs/src/seeds/SysUser.json +1 -0
- package/templates/cjs/src/services/database.js +15 -0
- package/templates/cjs/src/utils/config.js +65 -0
- package/templates/cjs/src/utils/logger.js +53 -0
- package/templates/cjs/test/index.js +13 -0
- package/templates/mjs/ecosystem.config.js +32 -0
- package/templates/mjs/eslint.config.js +25 -0
- package/templates/mjs/package.json +46 -0
- package/templates/mjs/src/app.js +97 -0
- package/templates/mjs/src/commands/migrate.js +38 -0
- package/templates/mjs/src/commands/seed.js +63 -0
- package/templates/mjs/src/controllers/hello.js +33 -0
- package/templates/mjs/src/controllers/user.js +101 -0
- package/templates/mjs/src/cronActions/cleanup.js +9 -0
- package/templates/mjs/src/cronWorker.js +91 -0
- package/templates/mjs/src/index.js +8 -0
- package/templates/mjs/src/models/SysUser.js +40 -0
- package/templates/mjs/src/seeds/SysUser.json +1 -0
- package/templates/mjs/src/services/database.js +16 -0
- package/templates/mjs/src/utils/config.js +63 -0
- package/templates/mjs/src/utils/logger.js +52 -0
- package/templates/mjs/test/index.mjs +13 -0
- package/templates/ts/ecosystem.config.js +36 -0
- package/templates/ts/eslint.config.js +17 -0
- package/templates/ts/package.json +57 -0
- package/templates/ts/rollup.config.mjs +39 -0
- package/templates/ts/src/app.ts +96 -0
- package/templates/ts/src/commands/migrate.ts +38 -0
- package/templates/ts/src/commands/seed.ts +63 -0
- package/templates/ts/src/controllers/hello.ts +33 -0
- package/templates/ts/src/controllers/user.ts +101 -0
- package/templates/ts/src/cronActions/cleanup.ts +10 -0
- package/templates/ts/src/cronWorker.ts +90 -0
- package/templates/ts/src/index.ts +8 -0
- package/templates/ts/src/models/SysUser.ts +49 -0
- package/templates/ts/src/seeds/SysUser.json +1 -0
- package/templates/ts/src/services/database.ts +16 -0
- package/templates/ts/src/utils/config.ts +68 -0
- package/templates/ts/src/utils/logger.ts +63 -0
- package/templates/ts/test/index.mjs +13 -0
- package/templates/ts/tsconfig.json +20 -0
- package/test/index.mjs +137 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const createHttpError = require('http-errors')
|
|
2
|
+
const { db } = require('../services/database.js')
|
|
3
|
+
|
|
4
|
+
const { SysUser } = db.sequelize.models
|
|
5
|
+
|
|
6
|
+
module.exports = [
|
|
7
|
+
{
|
|
8
|
+
method: 'get',
|
|
9
|
+
path: '/api/users',
|
|
10
|
+
async handler(req) {
|
|
11
|
+
const { page = 1, limit = 10 } = req.query
|
|
12
|
+
const users = await SysUser.findAndCountAll({
|
|
13
|
+
offset: (Number(page) - 1) * Number(limit),
|
|
14
|
+
limit: Number(limit)
|
|
15
|
+
})
|
|
16
|
+
return {
|
|
17
|
+
list: users.rows,
|
|
18
|
+
total: users.count
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
method: 'get',
|
|
24
|
+
path: '/api/users/:id',
|
|
25
|
+
paramsSchema: {
|
|
26
|
+
type: 'object',
|
|
27
|
+
required: ['id'],
|
|
28
|
+
properties: { id: { type: 'integer', minimum: 1 } }
|
|
29
|
+
},
|
|
30
|
+
async handler(req) {
|
|
31
|
+
const { id } = req.validatedParams
|
|
32
|
+
const user = await SysUser.findByPk(id)
|
|
33
|
+
if (!user) {
|
|
34
|
+
throw new createHttpError.NotFound('User not found')
|
|
35
|
+
}
|
|
36
|
+
return user
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
method: 'post',
|
|
41
|
+
path: '/api/users',
|
|
42
|
+
bodySchema: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
required: ['username', 'password'],
|
|
45
|
+
properties: {
|
|
46
|
+
username: { type: 'string', minLength: 3 },
|
|
47
|
+
password: { type: 'string', minLength: 6 },
|
|
48
|
+
email: { type: 'string', format: 'email' },
|
|
49
|
+
phone: { type: 'string' }
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
async handler(req) {
|
|
53
|
+
return await SysUser.create(req.validatedBody)
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
method: 'put',
|
|
58
|
+
path: '/api/users/:id',
|
|
59
|
+
paramsSchema: {
|
|
60
|
+
type: 'object',
|
|
61
|
+
required: ['id'],
|
|
62
|
+
properties: { id: { type: 'integer', minimum: 1 } }
|
|
63
|
+
},
|
|
64
|
+
bodySchema: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {
|
|
67
|
+
password: { type: 'string', minLength: 6 },
|
|
68
|
+
email: { type: 'string', format: 'email' },
|
|
69
|
+
phone: { type: 'string' },
|
|
70
|
+
status: { type: 'integer' }
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
async handler(req) {
|
|
74
|
+
const { id } = req.validatedParams
|
|
75
|
+
const user = await SysUser.findByPk(id)
|
|
76
|
+
if (!user) {
|
|
77
|
+
throw new createHttpError.NotFound('User not found')
|
|
78
|
+
}
|
|
79
|
+
await user.update(req.validatedBody)
|
|
80
|
+
return user
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
method: 'delete',
|
|
85
|
+
path: '/api/users/:id',
|
|
86
|
+
paramsSchema: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
required: ['id'],
|
|
89
|
+
properties: { id: { type: 'integer', minimum: 1 } }
|
|
90
|
+
},
|
|
91
|
+
async handler(req) {
|
|
92
|
+
const { id } = req.validatedParams
|
|
93
|
+
const user = await SysUser.findByPk(id)
|
|
94
|
+
if (!user) {
|
|
95
|
+
throw new createHttpError.NotFound('User not found')
|
|
96
|
+
}
|
|
97
|
+
await user.destroy()
|
|
98
|
+
return { message: 'User deleted' }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const { logger } = require('../utils/logger.js')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 清理任务示例
|
|
5
|
+
*/
|
|
6
|
+
async function cleanup() {
|
|
7
|
+
logger.info(`[cron] Running cleanup task at ${new Date().toISOString()}`)
|
|
8
|
+
logger.info('[cron] Cleanup task completed')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = cleanup
|
|
12
|
+
module.exports.default = cleanup
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require('dotenv/config')
|
|
2
|
+
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const { CronFramework } = require('skweb-framework')
|
|
5
|
+
const { logger } = require('./utils/logger.js')
|
|
6
|
+
const { checkRequiredEnv } = require('./utils/config.js')
|
|
7
|
+
|
|
8
|
+
async function startCronWorker() {
|
|
9
|
+
logger.info('[cron-worker] Starting cron worker process...')
|
|
10
|
+
|
|
11
|
+
if (!checkRequiredEnv()) {
|
|
12
|
+
logger.error('[cron-worker] Configuration validation failed, exiting...')
|
|
13
|
+
process.exit(1)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const framework = new CronFramework({
|
|
17
|
+
db: {
|
|
18
|
+
host: process.env.DB_HOST || '127.0.0.1',
|
|
19
|
+
port: Number(process.env.DB_PORT) || 3306,
|
|
20
|
+
username: process.env.DB_USER || 'root',
|
|
21
|
+
password: process.env.DB_PASS || '',
|
|
22
|
+
database: process.env.DB_NAME || 'test-project',
|
|
23
|
+
dialect: process.env.DB_DIALECT || 'mysql',
|
|
24
|
+
charset: process.env.DB_CHARSET || 'utf8mb4',
|
|
25
|
+
underscored: true,
|
|
26
|
+
logging: process.env.DEBUG_DB === 'true' ? (msg) => logger.debug(msg) : false
|
|
27
|
+
},
|
|
28
|
+
redis: {
|
|
29
|
+
host: process.env.REDIS_HOST || '127.0.0.1',
|
|
30
|
+
port: Number(process.env.REDIS_PORT) || 6379,
|
|
31
|
+
password: process.env.REDIS_PASSWORD || undefined
|
|
32
|
+
},
|
|
33
|
+
channel: 'cron:task:cmd',
|
|
34
|
+
logger,
|
|
35
|
+
onSaveTask: async (taskData) => {
|
|
36
|
+
const SysCronTask = framework.db?.sequelize?.models.SysCronTask
|
|
37
|
+
if (SysCronTask) {
|
|
38
|
+
await SysCronTask.update(
|
|
39
|
+
{ cache: taskData.cache, data: taskData.data },
|
|
40
|
+
{ where: { id: taskData.taskId } }
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
loadTasks: async () => {
|
|
45
|
+
const SysCronTask = framework.db?.sequelize?.models.SysCronTask
|
|
46
|
+
if (!SysCronTask) return []
|
|
47
|
+
return (await SysCronTask.findAll()).map((t) => ({
|
|
48
|
+
id: String(t.id),
|
|
49
|
+
cronTime: t.cronTime,
|
|
50
|
+
method: t.method
|
|
51
|
+
}))
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
await framework.runPluginBeforeSetup()
|
|
56
|
+
await framework.setupModels(path.join(__dirname, './models'))
|
|
57
|
+
await framework.loadPluginModels()
|
|
58
|
+
await framework.runPluginAfterSetup()
|
|
59
|
+
|
|
60
|
+
await framework.loadCronActions(path.join(__dirname, './cronActions'))
|
|
61
|
+
await framework.startWorker()
|
|
62
|
+
framework.start()
|
|
63
|
+
logger.info('[cron-worker] Cron worker started')
|
|
64
|
+
|
|
65
|
+
process.on('SIGINT', async () => {
|
|
66
|
+
logger.info('[cron-worker] Received SIGINT, shutting down...')
|
|
67
|
+
await framework.stop()
|
|
68
|
+
process.exit(0)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
process.on('SIGTERM', async () => {
|
|
72
|
+
logger.info('[cron-worker] Received SIGTERM, shutting down...')
|
|
73
|
+
await framework.stop()
|
|
74
|
+
process.exit(0)
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (require.main === module) {
|
|
79
|
+
startCronWorker().catch((err) => {
|
|
80
|
+
logger.error('[cron-worker] Fatal error:', err)
|
|
81
|
+
process.exit(1)
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { startCronWorker }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const { Model, DataTypes } = require('sequelize')
|
|
2
|
+
|
|
3
|
+
module.exports = (sequelize) => {
|
|
4
|
+
class SysUser extends Model {}
|
|
5
|
+
SysUser.init({
|
|
6
|
+
id: {
|
|
7
|
+
type: DataTypes.INTEGER,
|
|
8
|
+
primaryKey: true,
|
|
9
|
+
autoIncrement: true
|
|
10
|
+
},
|
|
11
|
+
username: {
|
|
12
|
+
type: DataTypes.STRING(50),
|
|
13
|
+
allowNull: false,
|
|
14
|
+
unique: true
|
|
15
|
+
},
|
|
16
|
+
password: {
|
|
17
|
+
type: DataTypes.STRING(255),
|
|
18
|
+
allowNull: false
|
|
19
|
+
},
|
|
20
|
+
email: {
|
|
21
|
+
type: DataTypes.STRING(100)
|
|
22
|
+
},
|
|
23
|
+
phone: {
|
|
24
|
+
type: DataTypes.STRING(20)
|
|
25
|
+
},
|
|
26
|
+
status: {
|
|
27
|
+
type: DataTypes.TINYINT,
|
|
28
|
+
defaultValue: 1
|
|
29
|
+
}
|
|
30
|
+
}, {
|
|
31
|
+
sequelize,
|
|
32
|
+
modelName: 'SysUser',
|
|
33
|
+
tableName: 'sys_users',
|
|
34
|
+
timestamps: true,
|
|
35
|
+
underscored: true
|
|
36
|
+
})
|
|
37
|
+
return SysUser
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"id": 1, "username": "admin", "password": "$2b$10$example", "email": "admin@example.com", "phone": "13800138000", "status": 1}]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const { Database } = require('skweb-sequelize')
|
|
2
|
+
|
|
3
|
+
const db = new Database({
|
|
4
|
+
host: process.env.DB_HOST || '127.0.0.1',
|
|
5
|
+
port: Number(process.env.DB_PORT) || 3306,
|
|
6
|
+
username: process.env.DB_USER || 'root',
|
|
7
|
+
password: process.env.DB_PASS || '',
|
|
8
|
+
database: process.env.DB_NAME || '{{name}}',
|
|
9
|
+
dialect: process.env.DB_DIALECT || 'mysql',
|
|
10
|
+
charset: process.env.DB_CHARSET || 'utf8mb4',
|
|
11
|
+
underscored: true,
|
|
12
|
+
logging: process.env.DEBUG_DB === 'true'
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
module.exports = { db }
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const { logger } = require('./logger.js')
|
|
2
|
+
|
|
3
|
+
function validateConfig() {
|
|
4
|
+
const errors = []
|
|
5
|
+
|
|
6
|
+
if (!process.env.DB_HOST) {
|
|
7
|
+
logger.warn('[config] DB_HOST not set, using default: 127.0.0.1')
|
|
8
|
+
}
|
|
9
|
+
if (!process.env.DB_NAME) {
|
|
10
|
+
logger.warn('[config] DB_NAME not set, using default: test-project')
|
|
11
|
+
}
|
|
12
|
+
if (!process.env.DB_USER) {
|
|
13
|
+
logger.warn('[config] DB_USER not set, using default: root')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (process.env.JWT_SECRET === 'please-change-this-in-production') {
|
|
17
|
+
logger.warn('[config] WARNING: JWT_SECRET is using default value, please change in production!')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!process.env.REDIS_HOST) {
|
|
21
|
+
logger.warn('[config] REDIS_HOST not set, using default: 127.0.0.1')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const port = Number(process.env.PORT)
|
|
25
|
+
if (port && (port < 1 || port > 65535)) {
|
|
26
|
+
errors.push('PORT must be between 1 and 65535')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const dbPort = Number(process.env.DB_PORT)
|
|
30
|
+
if (dbPort && (dbPort < 1 || dbPort > 65535)) {
|
|
31
|
+
errors.push('DB_PORT must be between 1 and 65535')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const redisPort = Number(process.env.REDIS_PORT)
|
|
35
|
+
if (redisPort && (redisPort < 1 || redisPort > 65535)) {
|
|
36
|
+
errors.push('REDIS_PORT must be between 1 and 65535')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
isValid: errors.length === 0,
|
|
41
|
+
errors
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function checkRequiredEnv() {
|
|
46
|
+
const result = validateConfig()
|
|
47
|
+
|
|
48
|
+
if (result.errors.length > 0) {
|
|
49
|
+
logger.error('[config] Configuration validation failed:')
|
|
50
|
+
result.errors.forEach((err) => logger.error(` - ${err}`))
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (process.env.NODE_ENV === 'production') {
|
|
55
|
+
if (!process.env.JWT_SECRET || process.env.JWT_SECRET === 'please-change-this-in-production') {
|
|
56
|
+
logger.error('[config] JWT_SECRET must be set in production!')
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
logger.info('[config] Configuration validation passed')
|
|
62
|
+
return true
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { validateConfig, checkRequiredEnv }
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Winston 日志配置
|
|
3
|
+
*/
|
|
4
|
+
const winston = require('winston')
|
|
5
|
+
const path = require('path')
|
|
6
|
+
const fs = require('fs')
|
|
7
|
+
|
|
8
|
+
const LOG_LEVEL = process.env.LOG_LEVEL || 'info'
|
|
9
|
+
const LOG_DIR = process.env.LOG_DIR || path.join(process.cwd(), 'logs')
|
|
10
|
+
const LOG_CONSOLE = process.env.LOG_CONSOLE !== 'false'
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(LOG_DIR)) {
|
|
13
|
+
fs.mkdirSync(LOG_DIR, { recursive: true })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { combine, timestamp, printf, colorize, errors, splat, json } = winston.format
|
|
17
|
+
|
|
18
|
+
const consoleFormat = printf(({ level, message, timestamp: ts, stack }) => {
|
|
19
|
+
return `${ts} [${level}] ${stack || message}`
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const fileFormat = combine(timestamp(), errors({ stack: true }), splat(), json())
|
|
23
|
+
|
|
24
|
+
const logger = winston.createLogger({
|
|
25
|
+
level: LOG_LEVEL,
|
|
26
|
+
format: combine(timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }), errors({ stack: true }), splat()),
|
|
27
|
+
transports: [
|
|
28
|
+
new winston.transports.File({
|
|
29
|
+
filename: path.join(LOG_DIR, 'error.log'),
|
|
30
|
+
level: 'error',
|
|
31
|
+
format: fileFormat,
|
|
32
|
+
maxsize: 10 * 1024 * 1024,
|
|
33
|
+
maxFiles: 10
|
|
34
|
+
}),
|
|
35
|
+
new winston.transports.File({
|
|
36
|
+
filename: path.join(LOG_DIR, 'combined.log'),
|
|
37
|
+
format: fileFormat,
|
|
38
|
+
maxsize: 10 * 1024 * 1024,
|
|
39
|
+
maxFiles: 10
|
|
40
|
+
})
|
|
41
|
+
]
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
if (LOG_CONSOLE) {
|
|
45
|
+
logger.add(
|
|
46
|
+
new winston.transports.Console({
|
|
47
|
+
format: combine(colorize(), consoleFormat)
|
|
48
|
+
})
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { logger }
|
|
53
|
+
module.exports.default = logger
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const { expect } = require('chai')
|
|
2
|
+
|
|
3
|
+
describe('SKWeb App Tests', function() {
|
|
4
|
+
describe('Basic Tests', function() {
|
|
5
|
+
it('should pass basic test', function() {
|
|
6
|
+
expect(true).to.equal(true)
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it('should have correct package structure', function() {
|
|
10
|
+
expect('SKWeb').to.be.a('string')
|
|
11
|
+
})
|
|
12
|
+
})
|
|
13
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
apps: [
|
|
3
|
+
{
|
|
4
|
+
name: '{{name}}-web',
|
|
5
|
+
script: 'src/index.js',
|
|
6
|
+
instances: 'max',
|
|
7
|
+
exec_mode: 'cluster',
|
|
8
|
+
env: {
|
|
9
|
+
NODE_ENV: 'production',
|
|
10
|
+
PORT: 3000
|
|
11
|
+
},
|
|
12
|
+
max_memory_restart: '512M',
|
|
13
|
+
error_file: 'logs/web-err.log',
|
|
14
|
+
out_file: 'logs/web-out.log',
|
|
15
|
+
log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: '{{name}}-cron',
|
|
19
|
+
script: 'src/cronWorker.js',
|
|
20
|
+
instances: 1,
|
|
21
|
+
exec_mode: 'fork',
|
|
22
|
+
autorestart: true,
|
|
23
|
+
env: {
|
|
24
|
+
NODE_ENV: 'production'
|
|
25
|
+
},
|
|
26
|
+
max_memory_restart: '512M',
|
|
27
|
+
error_file: 'logs/cron-err.log',
|
|
28
|
+
out_file: 'logs/cron-out.log',
|
|
29
|
+
log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
|
|
3
|
+
export default [
|
|
4
|
+
{ ignores: ['dist', 'node_modules', 'logs', 'test'] },
|
|
5
|
+
js.configs.recommended,
|
|
6
|
+
{
|
|
7
|
+
languageOptions: {
|
|
8
|
+
ecmaVersion: 2022,
|
|
9
|
+
sourceType: 'module',
|
|
10
|
+
globals: {
|
|
11
|
+
process: 'readonly',
|
|
12
|
+
console: 'readonly',
|
|
13
|
+
Buffer: 'readonly',
|
|
14
|
+
__dirname: 'readonly',
|
|
15
|
+
__filename: 'readonly',
|
|
16
|
+
require: 'readonly',
|
|
17
|
+
module: 'readonly',
|
|
18
|
+
exports: 'writable'
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
rules: {
|
|
22
|
+
'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "{{description}}",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "pm2 start ecosystem.config.js",
|
|
8
|
+
"stop": "pm2 stop all",
|
|
9
|
+
"restart": "pm2 restart all",
|
|
10
|
+
"delete": "pm2 delete all",
|
|
11
|
+
"logs": "pm2 logs",
|
|
12
|
+
"web:dev": "npx dotenvx run -- node --watch src/index.js",
|
|
13
|
+
"cron:dev": "npx dotenvx run -- node --watch src/cronWorker.js",
|
|
14
|
+
"seed": "npx dotenvx run -- node src/commands/seed.js",
|
|
15
|
+
"migrate": "npx dotenvx run -- node src/commands/migrate.js",
|
|
16
|
+
"build": "cp -r src dist",
|
|
17
|
+
"lint": "eslint --fix --ext .js src",
|
|
18
|
+
"test": "npx dotenvx run -- mocha test/**/*.mjs",
|
|
19
|
+
"clean": "rm -rf dist",
|
|
20
|
+
"clean:temp": "rm -rf .rollup.cache"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@dotenvx/dotenvx": "^1.71.0",
|
|
24
|
+
"http-errors": "^2.0.0",
|
|
25
|
+
"skweb-express": "^1.0.0",
|
|
26
|
+
"skweb-express-jwt": "^1.0.0",
|
|
27
|
+
"skweb-express-logger": "^1.0.0",
|
|
28
|
+
"skweb-sequelize": "^1.0.0",
|
|
29
|
+
"skweb-redis": "^1.0.0",
|
|
30
|
+
"skweb-cron": "^1.0.0",
|
|
31
|
+
"skweb-framework": "^1.0.0",
|
|
32
|
+
"skweb-plugin": "^1.0.0",
|
|
33
|
+
"skweb-util": "^1.0.0",
|
|
34
|
+
"winston": "^3.17.0",
|
|
35
|
+
"sequelize": "^6.37.7"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"chai": "^6.2.1",
|
|
39
|
+
"mocha": "^11.7.5",
|
|
40
|
+
"eslint": "^9.39.1",
|
|
41
|
+
"@eslint/js": "^9.39.1"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=20.18.1"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import 'dotenv/config'
|
|
2
|
+
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
import express from 'skweb-express'
|
|
6
|
+
import expressLogger from 'skweb-express-logger'
|
|
7
|
+
import logger from './utils/logger.js'
|
|
8
|
+
import { checkRequiredEnv } from './utils/config.js'
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
11
|
+
const __dirname = path.dirname(__filename)
|
|
12
|
+
|
|
13
|
+
async function bootstrap() {
|
|
14
|
+
logger.info('[app] Initializing web process...')
|
|
15
|
+
|
|
16
|
+
if (!checkRequiredEnv()) {
|
|
17
|
+
logger.error('[app] Configuration validation failed, exiting...')
|
|
18
|
+
process.exit(1)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { WebFramework } = await import('skweb-framework')
|
|
22
|
+
|
|
23
|
+
const framework = new WebFramework({
|
|
24
|
+
db: {
|
|
25
|
+
host: process.env.DB_HOST || '127.0.0.1',
|
|
26
|
+
port: Number(process.env.DB_PORT) || 3306,
|
|
27
|
+
username: process.env.DB_USER || 'root',
|
|
28
|
+
password: process.env.DB_PASS || '',
|
|
29
|
+
database: process.env.DB_NAME || 'test-project',
|
|
30
|
+
dialect: process.env.DB_DIALECT || 'mysql',
|
|
31
|
+
charset: process.env.DB_CHARSET || 'utf8mb4',
|
|
32
|
+
underscored: true,
|
|
33
|
+
logging: process.env.DEBUG_DB === 'true' ? (msg) => logger.debug(msg) : false
|
|
34
|
+
},
|
|
35
|
+
redis: {
|
|
36
|
+
host: process.env.REDIS_HOST || '127.0.0.1',
|
|
37
|
+
port: Number(process.env.REDIS_PORT) || 6379,
|
|
38
|
+
password: process.env.REDIS_PASSWORD || undefined
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
await framework.runPluginBeforeSetup()
|
|
43
|
+
await framework.setupModels(path.join(__dirname, './models'))
|
|
44
|
+
await framework.loadPluginModels()
|
|
45
|
+
await framework.runPluginAfterSetup()
|
|
46
|
+
|
|
47
|
+
const app = framework.createApp()
|
|
48
|
+
|
|
49
|
+
app.disable('x-powered-by')
|
|
50
|
+
app.disable('etag')
|
|
51
|
+
|
|
52
|
+
app.set('trust proxy', true)
|
|
53
|
+
|
|
54
|
+
app.set('wrap response', true)
|
|
55
|
+
app.setOkCode(0)
|
|
56
|
+
app.setOkMessage('success')
|
|
57
|
+
|
|
58
|
+
app.setJWT('default', {
|
|
59
|
+
secret: process.env.JWT_SECRET || 'please-change-this-in-production',
|
|
60
|
+
algorithms: ['HS256'],
|
|
61
|
+
sign: { expiresIn: process.env.JWT_EXPIRES_IN || '7d' },
|
|
62
|
+
requestProperty: 'resolvedToken'
|
|
63
|
+
})
|
|
64
|
+
app.setJwtHintHeader('JwtHint')
|
|
65
|
+
|
|
66
|
+
app.use(expressLogger())
|
|
67
|
+
app.use(express.static(path.join(__dirname, '..', 'public')))
|
|
68
|
+
|
|
69
|
+
await framework.loadControllers(path.join(__dirname, './controllers'))
|
|
70
|
+
await framework.loadPluginControllers()
|
|
71
|
+
|
|
72
|
+
framework.start(Number(process.env.PORT) || 3000)
|
|
73
|
+
|
|
74
|
+
logger.info('[app] Initialization complete (web process)')
|
|
75
|
+
|
|
76
|
+
process.on('SIGINT', async () => {
|
|
77
|
+
logger.info('[app] Received SIGINT, shutting down gracefully...')
|
|
78
|
+
await framework.stop()
|
|
79
|
+
process.exit(0)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
process.on('SIGTERM', async () => {
|
|
83
|
+
logger.info('[app] Received SIGTERM, shutting down gracefully...')
|
|
84
|
+
await framework.stop()
|
|
85
|
+
process.exit(0)
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}`
|
|
90
|
+
if (isMainModule) {
|
|
91
|
+
bootstrap().catch((err) => {
|
|
92
|
+
logger.error('[app] Fatal error:', err)
|
|
93
|
+
process.exit(1)
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export { bootstrap }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import 'dotenv/config'
|
|
2
|
+
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
import { Database } from 'skweb-sequelize'
|
|
6
|
+
import logger from '../utils/logger.js'
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
9
|
+
const __dirname = path.dirname(__filename)
|
|
10
|
+
|
|
11
|
+
async function runMigrate() {
|
|
12
|
+
logger.info('[migrate] Running database migrations...')
|
|
13
|
+
|
|
14
|
+
const db = new Database({
|
|
15
|
+
host: process.env.DB_HOST || '127.0.0.1',
|
|
16
|
+
port: Number(process.env.DB_PORT) || 3306,
|
|
17
|
+
username: process.env.DB_USER || 'root',
|
|
18
|
+
password: process.env.DB_PASS || '',
|
|
19
|
+
database: process.env.DB_NAME || 'test-project',
|
|
20
|
+
dialect: process.env.DB_DIALECT || 'mysql',
|
|
21
|
+
charset: process.env.DB_CHARSET || 'utf8mb4',
|
|
22
|
+
underscored: true,
|
|
23
|
+
logging: process.env.DEBUG_DB === 'true' ? (msg) => logger.debug(msg) : false
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
await db.loadModelsDir(path.join(__dirname, '..', 'models'))
|
|
27
|
+
db.associateAllModels()
|
|
28
|
+
await db.sequelize.sync({ alter: true })
|
|
29
|
+
logger.info('[migrate] Database schema synced')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}`
|
|
33
|
+
if (isMainModule) {
|
|
34
|
+
runMigrate().catch((err) => {
|
|
35
|
+
logger.error('[migrate] Fatal error:', err)
|
|
36
|
+
process.exit(1)
|
|
37
|
+
})
|
|
38
|
+
}
|