@re-shell/cli
Version:
Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja
1,937 lines (1,662 loc) • 46.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.thinkjsTemplate = void 0;
exports.thinkjsTemplate = {
id: 'thinkjs',
name: 'ThinkJS',
description: 'Modern Node.js MVC framework with ES6/ES7 support and auto-loading',
icon: '🚀',
framework: 'thinkjs',
displayName: 'ThinkJS MVC Framework',
language: 'javascript',
version: '3.2.14',
tags: ['mvc', 'es6', 'es7', 'auto-loading', 'orm', 'websocket', 'cron', 'typescript'],
dependencies: {
'think-cache': '^1.1.0',
'think-cache-file': '^1.1.0',
'think-cache-redis': '^1.1.0',
'think-cache-memcache': '^1.0.2',
'think-session': '^1.1.0',
'think-session-file': '^1.0.1',
'think-session-redis': '^1.0.0',
'think-view': '^1.1.0',
'think-view-nunjucks': '^1.0.7',
'think-view-ejs': '^1.0.0',
'think-model': '^1.5.0',
'think-model-mysql': '^1.1.0',
'think-model-postgresql': '^1.0.1',
'think-model-sqlite': '^1.0.0',
'think-websocket': '^1.0.3',
'think-websocket-socket.io': '^1.0.1',
'think-i18n': '^1.2.3',
'think-validator': '^1.5.0',
'thinkjs': '^3.2.14',
'think-typescript': '^1.1.0',
'ioredis': '^5.3.2',
'jsonwebtoken': '^9.0.0',
'bcryptjs': '^2.4.3',
'moment': '^2.29.4',
'axios': '^1.5.0',
'winston': '^3.10.0',
'winston-daily-rotate-file': '^4.7.1'
},
files: {
'package.json': `{
"name": "thinkjs-microservice",
"description": "ThinkJS microservice with modern JavaScript features",
"version": "1.0.0",
"author": "",
"scripts": {
"start": "node development.js",
"dev": "node development.js",
"build": "npm run compile",
"compile": "babel src/ --out-dir app/",
"lint": "eslint src/",
"lint-fix": "eslint --fix src/",
"test": "THINK_UNIT_TEST=1 nyc mocha test/unit",
"test-integration": "mocha test/integration",
"coverage": "nyc report --reporter=html",
"production": "node production.js",
"docker": "docker-compose up -d"
},
"dependencies": {
"think-cache": "^1.1.0",
"think-cache-file": "^1.1.0",
"think-cache-redis": "^1.1.0",
"think-cache-memcache": "^1.0.2",
"think-session": "^1.1.0",
"think-session-file": "^1.0.1",
"think-session-redis": "^1.0.0",
"think-view": "^1.1.0",
"think-view-nunjucks": "^1.0.7",
"think-view-ejs": "^1.0.0",
"think-model": "^1.5.0",
"think-model-mysql": "^1.1.0",
"think-model-postgresql": "^1.0.1",
"think-model-sqlite": "^1.0.0",
"think-websocket": "^1.0.3",
"think-websocket-socket.io": "^1.0.1",
"think-i18n": "^1.2.3",
"think-validator": "^1.5.0",
"thinkjs": "^3.2.14",
"think-typescript": "^1.1.0",
"ioredis": "^5.3.2",
"jsonwebtoken": "^9.0.0",
"bcryptjs": "^2.4.3",
"moment": "^2.29.4",
"axios": "^1.5.0",
"winston": "^3.10.0",
"winston-daily-rotate-file": "^4.7.1"
},
"devDependencies": {
"@babel/cli": "^7.22.10",
"@babel/core": "^7.22.10",
"@babel/preset-env": "^7.22.10",
"@types/node": "^20.5.0",
"eslint": "^8.47.0",
"eslint-config-think": "^1.0.2",
"mocha": "^10.2.0",
"nyc": "^15.1.0",
"supertest": "^6.3.3",
"think-watcher": "^3.0.3",
"typescript": "^5.1.6"
},
"engines": {
"node": ">=14.0.0"
},
"readmeFilename": "README.md",
"thinkjs": {
"metadata": {
"name": "thinkjs-microservice",
"description": "ThinkJS microservice with modern JavaScript features",
"author": "",
"babel": true
}
}
}`,
'development.js': `const Application = require('thinkjs');
const watcher = require('think-watcher');
const babel = require('@babel/core');
const path = require('path');
const instance = new Application({
ROOT_PATH: __dirname,
watcher: watcher,
transpiler: [babel, {
presets: [['@babel/preset-env', { modules: false }]]
}],
notifier: true,
env: 'development'
});
instance.run();`,
'production.js': `const Application = require('thinkjs');
const instance = new Application({
ROOT_PATH: __dirname,
proxy: true,
env: 'production'
});
instance.run();`,
'src/config/config.js': `module.exports = {
// Service identification
serviceName: 'thinkjs-microservice',
serviceVersion: '1.0.0',
// Server configuration
port: parseInt(process.env.PORT) || 8360,
host: '0.0.0.0',
workers: 0, // 0 = number of CPUs
// Security
stickyCluster: true,
enableCORS: true,
// Logging
logLevel: process.env.LOG_LEVEL || 'info',
// API configuration
apiPrefix: '/api/v1',
apiVersion: 'v1',
// Health check
healthCheckPath: '/health',
readinessPath: '/ready'
};`,
'src/config/adapter.js': `const fileCache = require('think-cache-file');
const redisCache = require('think-cache-redis');
const memcacheCache = require('think-cache-memcache');
const nunjucks = require('think-view-nunjucks');
const ejs = require('think-view-ejs');
const fileSession = require('think-session-file');
const redisSession = require('think-session-redis');
const mysql = require('think-model-mysql');
const postgresql = require('think-model-postgresql');
const sqlite = require('think-model-sqlite');
const socketio = require('think-websocket-socket.io');
const path = require('path');
// Cache adapter configuration
exports.cache = {
type: 'file',
common: {
timeout: 24 * 60 * 60 * 1000 // 24 hours
},
file: {
handle: fileCache,
cachePath: path.join(think.ROOT_PATH, 'runtime/cache')
},
redis: {
handle: redisCache,
host: process.env.REDIS_HOST || '127.0.0.1',
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD || '',
db: process.env.REDIS_DB || 0
},
memcache: {
handle: memcacheCache,
hosts: process.env.MEMCACHE_HOSTS || '127.0.0.1:11211',
maxKeySize: 1000000,
poolSize: 10
}
};
// Session adapter configuration
exports.session = {
type: 'file',
common: {
cookie: {
name: 'thinkjs',
keys: ['werwer', 'werwrr'],
signed: true,
httponly: true,
sameSite: 'strict'
}
},
file: {
handle: fileSession,
sessionPath: path.join(think.ROOT_PATH, 'runtime/session')
},
redis: {
handle: redisSession,
host: process.env.REDIS_HOST || '127.0.0.1',
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD || '',
db: process.env.REDIS_DB || 1
}
};
// View adapter configuration
exports.view = {
type: 'nunjucks',
common: {
viewPath: path.join(think.ROOT_PATH, 'view'),
sep: '_',
extname: '.html'
},
nunjucks: {
handle: nunjucks,
options: {
autoescape: true,
noCache: think.env === 'development',
throwOnUndefined: false
}
},
ejs: {
handle: ejs,
options: {
cache: think.env !== 'development'
}
}
};
// Model adapter configuration
exports.model = {
type: 'mysql',
common: {
logConnect: think.env === 'development',
logSql: think.env === 'development',
logger: msg => think.logger.info(msg)
},
mysql: {
handle: mysql,
database: process.env.DB_NAME || 'thinkjs',
prefix: process.env.DB_PREFIX || 'tk_',
encoding: 'utf8mb4',
host: process.env.DB_HOST || '127.0.0.1',
port: process.env.DB_PORT || 3306,
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || '',
dateStrings: true,
connectionLimit: 10,
acquireTimeout: 60000
},
postgresql: {
handle: postgresql,
database: process.env.PG_DATABASE || 'thinkjs',
user: process.env.PG_USER || 'postgres',
password: process.env.PG_PASSWORD || '',
host: process.env.PG_HOST || '127.0.0.1',
port: process.env.PG_PORT || 5432,
connectionLimit: 10
},
sqlite: {
handle: sqlite,
path: path.join(think.ROOT_PATH, 'runtime/sqlite'),
database: 'thinkjs'
}
};
// WebSocket adapter configuration
exports.websocket = {
type: 'socketio',
common: {
// common config
},
socketio: {
handle: socketio,
allowOrigin: process.env.WEBSOCKET_ORIGINS || 'http://localhost:*',
path: '/socket.io',
adapter: null,
messages: {
open: 'home/websocket/open',
addUser: 'home/websocket/addUser',
typing: 'home/websocket/typing',
chat: 'home/websocket/chat'
}
}
};`,
'src/config/middleware.js': `const path = require('path');
module.exports = [
{
handle: 'meta',
options: {
logRequest: think.env === 'development',
sendResponseTime: true,
sendPoweredBy: false,
requestTimeout: 30 * 1000 // 30 seconds
}
},
{
handle: 'resource',
enable: true,
options: {
root: path.join(think.ROOT_PATH, 'www'),
publicPath: /^\\/static/
}
},
{
handle: 'trace',
enable: think.env === 'development',
options: {
debug: true,
error(err) {
think.logger.error(err);
}
}
},
{
handle: 'payload',
options: {
keepExtensions: true,
limit: '5mb',
encoding: 'utf-8',
multiples: true
}
},
{
handle: 'router',
options: {
suffix: ['.html'],
enableDefaultRouter: true,
optimizeHomepageRouter: true
}
},
'logic',
'controller'
];`,
'src/config/router.js': `module.exports = [
// RESTful API routes
['/api/v1/users', 'api/user', 'rest'],
['/api/v1/products', 'api/product', 'rest'],
['/api/v1/orders', 'api/order', 'rest'],
// Custom routes
['/api/v1/auth/login', 'api/auth/login', 'post'],
['/api/v1/auth/logout', 'api/auth/logout', 'post'],
['/api/v1/auth/refresh', 'api/auth/refresh', 'post'],
['/api/v1/auth/verify', 'api/auth/verify', 'get'],
// WebSocket routes
['/ws', 'websocket/index', 'websocket'],
// Health check routes
['/health', 'common/health/check', 'get'],
['/ready', 'common/health/ready', 'get'],
['/metrics', 'common/metrics/index', 'get'],
// File upload
['/api/v1/upload', 'api/upload/file', 'post'],
['/api/v1/upload/image', 'api/upload/image', 'post'],
// Batch operations
['/api/v1/batch', 'api/batch/process', 'post'],
// Search and filter
['/api/v1/search', 'api/search/query', 'get'],
['/api/v1/filter/:resource', 'api/filter/apply', 'post']
];`,
'src/config/crontab.js': `module.exports = [
{
cron: '0 0 * * *', // Daily at midnight
handle: 'cron/daily/cleanup',
type: 'one' // Run on one worker only
},
{
cron: '*/5 * * * *', // Every 5 minutes
handle: 'cron/monitor/health',
type: 'one'
},
{
cron: '0 */1 * * *', // Every hour
handle: 'cron/sync/data',
type: 'one'
},
{
cron: '0 2 * * 0', // Weekly on Sunday at 2 AM
handle: 'cron/backup/database',
type: 'one'
},
{
cron: '*/30 * * * *', // Every 30 minutes
handle: 'cron/cache/refresh',
type: 'all' // Run on all workers
},
{
interval: '10m', // Every 10 minutes using interval syntax
handle: 'cron/analytics/aggregate',
immediate: false,
type: 'one'
}
];`,
'src/config/locale/en.js': `module.exports = {
// Common messages
WELCOME_MESSAGE: 'Welcome to ThinkJS Microservice',
SUCCESS: 'Operation successful',
ERROR: 'An error occurred',
NOT_FOUND: 'Resource not found',
UNAUTHORIZED: 'Unauthorized access',
FORBIDDEN: 'Access forbidden',
// Validation messages
REQUIRED_FIELD: '{field} is required',
INVALID_EMAIL: 'Invalid email address',
INVALID_FORMAT: 'Invalid {field} format',
MIN_LENGTH: '{field} must be at least {min} characters',
MAX_LENGTH: '{field} must not exceed {max} characters',
// Authentication
LOGIN_SUCCESS: 'Login successful',
LOGIN_FAILED: 'Invalid credentials',
LOGOUT_SUCCESS: 'Logout successful',
TOKEN_EXPIRED: 'Authentication token expired',
TOKEN_INVALID: 'Invalid authentication token',
// User messages
USER_CREATED: 'User created successfully',
USER_UPDATED: 'User updated successfully',
USER_DELETED: 'User deleted successfully',
USER_NOT_FOUND: 'User not found',
EMAIL_EXISTS: 'Email already exists',
// API messages
RATE_LIMIT_EXCEEDED: 'Rate limit exceeded',
INVALID_REQUEST: 'Invalid request parameters',
SERVER_ERROR: 'Internal server error',
SERVICE_UNAVAILABLE: 'Service temporarily unavailable'
};`,
'src/controller/base.js': `module.exports = class extends think.Controller {
async __before() {
// Global before hook for all controllers
this.startTime = Date.now();
// Set common headers
this.header('X-Service-Name', think.config('serviceName'));
this.header('X-Service-Version', think.config('serviceVersion'));
// Log request
think.logger.info(\`\${this.method} \${this.ctx.url} - \${this.ip}\`);
}
async __after() {
// Global after hook
const duration = Date.now() - this.startTime;
this.header('X-Response-Time', \`\${duration}ms\`);
// Log response
think.logger.info(\`Response sent in \${duration}ms - Status: \${this.ctx.status}\`);
}
/**
* Success response helper
*/
success(data = null, message = '') {
return this.json({
success: true,
data,
message: message || this.locale('SUCCESS'),
timestamp: new Date().toISOString()
});
}
/**
* Error response helper
*/
error(message = '', code = 400, data = null) {
this.ctx.status = code;
return this.json({
success: false,
error: {
message: message || this.locale('ERROR'),
code,
data
},
timestamp: new Date().toISOString()
});
}
/**
* Paginated response helper
*/
paginate(data, currentPage, pageSize, totalCount) {
const totalPages = Math.ceil(totalCount / pageSize);
return this.json({
success: true,
data,
pagination: {
currentPage,
pageSize,
totalCount,
totalPages,
hasNext: currentPage < totalPages,
hasPrev: currentPage > 1
},
timestamp: new Date().toISOString()
});
}
/**
* Get validated pagination parameters
*/
getPagination() {
const page = Math.max(1, parseInt(this.get('page')) || 1);
const pageSize = Math.min(100, Math.max(1, parseInt(this.get('pageSize')) || 20));
const offset = (page - 1) * pageSize;
return { page, pageSize, offset };
}
/**
* Check if request wants JSON response
*/
isJsonRequest() {
const accept = this.header('accept') || '';
return accept.includes('application/json') || this.isAjax || this.isPost;
}
};`,
'src/controller/api/user.js': `const Base = require('../base.js');
module.exports = class extends Base {
/**
* RESTful GET - Get all users or single user
*/
async getAction() {
const id = this.id;
if (id) {
// Get single user
const user = await this.model('user').where({ id }).find();
if (think.isEmpty(user)) {
return this.error(this.locale('USER_NOT_FOUND'), 404);
}
delete user.password; // Remove sensitive data
return this.success(user);
}
// Get all users with pagination
const { page, pageSize, offset } = this.getPagination();
const where = this.buildWhereConditions();
const [users, totalCount] = await Promise.all([
this.model('user')
.where(where)
.field('id,username,email,role,status,created_at,updated_at')
.limit(pageSize)
.offset(offset)
.order('created_at DESC')
.select(),
this.model('user').where(where).count()
]);
return this.paginate(users, page, pageSize, totalCount);
}
/**
* RESTful POST - Create new user
*/
async postAction() {
const data = this.post();
// Validate input
const validation = await this.validateUser(data);
if (!validation.valid) {
return this.error(validation.message, 400, validation.errors);
}
// Check if email exists
const exists = await this.model('user').where({ email: data.email }).find();
if (!think.isEmpty(exists)) {
return this.error(this.locale('EMAIL_EXISTS'), 409);
}
// Hash password
const bcrypt = require('bcryptjs');
data.password = await bcrypt.hash(data.password, 10);
// Create user
data.created_at = new Date();
data.updated_at = new Date();
const userId = await this.model('user').add(data);
const user = await this.model('user')
.field('id,username,email,role,status,created_at')
.where({ id: userId })
.find();
// Emit user created event
think.messenger.broadcast('user:created', user);
return this.success(user, this.locale('USER_CREATED'));
}
/**
* RESTful PUT - Update user
*/
async putAction() {
const id = this.id;
if (!id) {
return this.error(this.locale('INVALID_REQUEST'), 400);
}
const data = this.post();
delete data.id; // Prevent ID update
// Check if user exists
const user = await this.model('user').where({ id }).find();
if (think.isEmpty(user)) {
return this.error(this.locale('USER_NOT_FOUND'), 404);
}
// Validate update data
const validation = await this.validateUser(data, true);
if (!validation.valid) {
return this.error(validation.message, 400, validation.errors);
}
// Update password if provided
if (data.password) {
const bcrypt = require('bcryptjs');
data.password = await bcrypt.hash(data.password, 10);
}
// Update user
data.updated_at = new Date();
await this.model('user').where({ id }).update(data);
const updatedUser = await this.model('user')
.field('id,username,email,role,status,updated_at')
.where({ id })
.find();
return this.success(updatedUser, this.locale('USER_UPDATED'));
}
/**
* RESTful DELETE - Delete user
*/
async deleteAction() {
const id = this.id;
if (!id) {
return this.error(this.locale('INVALID_REQUEST'), 400);
}
// Check if user exists
const user = await this.model('user').where({ id }).find();
if (think.isEmpty(user)) {
return this.error(this.locale('USER_NOT_FOUND'), 404);
}
// Soft delete
await this.model('user').where({ id }).update({
status: 'deleted',
deleted_at: new Date(),
updated_at: new Date()
});
return this.success(null, this.locale('USER_DELETED'));
}
/**
* Build where conditions from query parameters
*/
buildWhereConditions() {
const where = { status: ['!=', 'deleted'] };
// Search by username or email
const search = this.get('search');
if (search) {
where._complex = {
username: ['like', \`%\${search}%\`],
email: ['like', \`%\${search}%\`],
_logic: 'or'
};
}
// Filter by role
const role = this.get('role');
if (role) {
where.role = role;
}
// Filter by status
const status = this.get('status');
if (status && status !== 'deleted') {
where.status = status;
}
return where;
}
/**
* Validate user data
*/
async validateUser(data, isUpdate = false) {
const rules = {
username: {
required: !isUpdate,
string: true,
min: 3,
max: 50,
regexp: /^[a-zA-Z0-9_-]+$/
},
email: {
required: !isUpdate,
email: true
},
password: {
required: !isUpdate,
string: true,
min: 8,
max: 100
},
role: {
in: ['admin', 'user', 'guest']
},
status: {
in: ['active', 'inactive', 'suspended']
}
};
const flag = await this.validate(rules, data);
if (!flag) {
return {
valid: false,
message: this.locale('INVALID_REQUEST'),
errors: this.validateErrors
};
}
return { valid: true };
}
};`,
'src/model/user.js': `module.exports = class extends think.Model {
// Define table schema
get schema() {
return {
id: {
type: 'int',
primary: true,
autoIncrement: true
},
username: {
type: 'string',
size: 50,
unique: true,
index: true
},
email: {
type: 'string',
size: 100,
unique: true,
index: true
},
password: {
type: 'string',
size: 255
},
role: {
type: 'string',
default: 'user',
index: true
},
status: {
type: 'string',
default: 'active',
index: true
},
profile: {
type: 'json',
default: {}
},
last_login_at: {
type: 'datetime'
},
created_at: {
type: 'datetime',
default: () => new Date()
},
updated_at: {
type: 'datetime',
default: () => new Date()
},
deleted_at: {
type: 'datetime'
}
};
}
// Define relations
get relation() {
return {
orders: {
type: think.Model.HAS_MANY,
model: 'order',
fKey: 'user_id'
},
profile: {
type: think.Model.HAS_ONE,
model: 'user_profile',
fKey: 'user_id'
},
roles: {
type: think.Model.MANY_TO_MANY,
model: 'role',
rModel: 'user_role',
fKey: 'user_id',
rfKey: 'role_id'
}
};
}
// Model hooks
beforeAdd(data) {
data.created_at = new Date();
data.updated_at = new Date();
return data;
}
beforeUpdate(data) {
data.updated_at = new Date();
return data;
}
afterFind(data, options) {
// Hide password field by default
if (data && !options.includePassword) {
delete data.password;
}
return data;
}
afterSelect(data, options) {
// Hide password field for all results
if (!options.includePassword) {
data.forEach(item => delete item.password);
}
return data;
}
// Custom methods
async findByEmail(email) {
return this.where({ email, status: ['!=', 'deleted'] }).find();
}
async findByUsername(username) {
return this.where({ username, status: ['!=', 'deleted'] }).find();
}
async updateLastLogin(userId) {
return this.where({ id: userId }).update({
last_login_at: new Date(),
updated_at: new Date()
});
}
async getActiveUsers(page = 1, pageSize = 20) {
const offset = (page - 1) * pageSize;
return this.where({ status: 'active' })
.field('id,username,email,role,created_at')
.limit(pageSize)
.offset(offset)
.order('created_at DESC')
.select();
}
async searchUsers(keyword, options = {}) {
const where = {
status: ['!=', 'deleted'],
_complex: {
username: ['like', \`%\${keyword}%\`],
email: ['like', \`%\${keyword}%\`],
_logic: 'or'
}
};
let query = this.where(where);
if (options.fields) {
query = query.field(options.fields);
}
if (options.limit) {
query = query.limit(options.limit);
}
return query.select();
}
// Transaction example
async createUserWithProfile(userData, profileData) {
const transaction = await this.startTrans();
try {
// Create user
const userId = await this.add(userData);
// Create profile
profileData.user_id = userId;
await this.model('user_profile').add(profileData);
await this.commit();
return userId;
} catch (error) {
await this.rollback();
throw error;
}
}
};`,
'src/controller/websocket/index.js': `const Base = require('../base.js');
module.exports = class extends Base {
constructor(...args) {
super(...args);
this.room = 'default';
this.users = new Map();
}
/**
* WebSocket connection opened
*/
async openAction() {
const socketId = this.wsData.socketId;
const userId = this.wsData.userId || \`guest_\${socketId}\`;
// Store user connection
this.users.set(socketId, {
id: userId,
socketId,
joinedAt: new Date(),
room: this.room
});
// Join default room
await this.broadcast('join', {
user: userId,
message: \`\${userId} joined the room\`,
timestamp: new Date()
});
// Send welcome message
this.emit('welcome', {
message: 'Welcome to ThinkJS WebSocket',
socketId,
userId,
room: this.room
});
// Send active users list
this.emit('users', Array.from(this.users.values()));
think.logger.info(\`WebSocket connected: \${socketId}\`);
}
/**
* Handle chat messages
*/
async chatAction() {
const { message, room = this.room } = this.wsData;
const socketId = this.wsData.socketId;
const user = this.users.get(socketId);
if (!user || !message) {
return this.emit('error', { message: 'Invalid chat request' });
}
const chatMessage = {
id: think.uuid('v4'),
userId: user.id,
message,
room,
timestamp: new Date()
};
// Store message in database
await this.model('chat_message').add(chatMessage);
// Broadcast to room
await this.broadcast('message', chatMessage, room);
// Log chat activity
think.logger.info(\`Chat message from \${user.id}: \${message}\`);
}
/**
* Handle typing indicator
*/
async typingAction() {
const { isTyping } = this.wsData;
const socketId = this.wsData.socketId;
const user = this.users.get(socketId);
if (!user) return;
// Broadcast typing status
await this.broadcast('typing', {
userId: user.id,
isTyping,
timestamp: new Date()
}, user.room, socketId);
}
/**
* Join a specific room
*/
async joinRoomAction() {
const { room } = this.wsData;
const socketId = this.wsData.socketId;
const user = this.users.get(socketId);
if (!user || !room) {
return this.emit('error', { message: 'Invalid room request' });
}
// Leave current room
await this.broadcast('leave', {
user: user.id,
message: \`\${user.id} left the room\`
}, user.room);
// Update user room
user.room = room;
this.users.set(socketId, user);
// Join new room
await this.broadcast('join', {
user: user.id,
message: \`\${user.id} joined the room\`
}, room);
// Confirm room change
this.emit('roomChanged', {
room,
message: \`You joined room: \${room}\`
});
}
/**
* Send private message
*/
async privateMessageAction() {
const { targetUserId, message } = this.wsData;
const socketId = this.wsData.socketId;
const sender = this.users.get(socketId);
if (!sender || !targetUserId || !message) {
return this.emit('error', { message: 'Invalid private message' });
}
// Find target user
const targetSocket = Array.from(this.users.entries())
.find(([_, user]) => user.id === targetUserId);
if (!targetSocket) {
return this.emit('error', { message: 'User not found' });
}
const privateMessage = {
id: think.uuid('v4'),
from: sender.id,
to: targetUserId,
message,
timestamp: new Date()
};
// Send to target user
this.emit('privateMessage', privateMessage, targetSocket[0]);
// Confirm to sender
this.emit('privateMessageSent', privateMessage);
}
/**
* Get chat history
*/
async historyAction() {
const { room = this.room, limit = 50 } = this.wsData;
const messages = await this.model('chat_message')
.where({ room })
.limit(limit)
.order('timestamp DESC')
.select();
this.emit('history', {
room,
messages: messages.reverse()
});
}
/**
* Handle disconnection
*/
async closeAction() {
const socketId = this.wsData.socketId;
const user = this.users.get(socketId);
if (user) {
// Remove from users map
this.users.delete(socketId);
// Notify others
await this.broadcast('leave', {
user: user.id,
message: \`\${user.id} disconnected\`,
timestamp: new Date()
}, user.room);
think.logger.info(\`WebSocket disconnected: \${socketId}\`);
}
}
/**
* Broadcast message to room
*/
async broadcast(event, data, room = this.room, excludeSocketId = null) {
const sockets = await this.getWebsocketSockets();
sockets.forEach(socket => {
if (socket.id !== excludeSocketId) {
const socketUser = this.users.get(socket.id);
if (socketUser && socketUser.room === room) {
this.emit(event, data, socket.id);
}
}
});
}
};`,
'src/controller/cron/daily/cleanup.js': `module.exports = class extends think.Controller {
async __call() {
const startTime = Date.now();
think.logger.info('Starting daily cleanup job...');
try {
// Clean up expired sessions
const sessionsCleaned = await this.cleanExpiredSessions();
// Clean up old logs
const logsCleaned = await this.cleanOldLogs();
// Clean up temporary files
const filesCleaned = await this.cleanTempFiles();
// Clean up soft-deleted records
const recordsCleaned = await this.cleanSoftDeletedRecords();
// Generate cleanup report
const report = {
sessionsCleaned,
logsCleaned,
filesCleaned,
recordsCleaned,
duration: Date.now() - startTime
};
// Store cleanup report
await this.model('cron_log').add({
job_name: 'daily_cleanup',
status: 'success',
report: JSON.stringify(report),
executed_at: new Date()
});
think.logger.info(\`Daily cleanup completed in \${report.duration}ms\`, report);
} catch (error) {
think.logger.error('Daily cleanup failed:', error);
await this.model('cron_log').add({
job_name: 'daily_cleanup',
status: 'failed',
error: error.message,
executed_at: new Date()
});
}
}
async cleanExpiredSessions() {
const expiredDate = new Date(Date.now() - 24 * 60 * 60 * 1000); // 24 hours ago
const count = await this.model('session')
.where({ updated_at: ['<', expiredDate] })
.delete();
return count;
}
async cleanOldLogs() {
const fs = require('fs').promises;
const path = require('path');
const logsDir = path.join(think.ROOT_PATH, 'runtime/logs');
let cleaned = 0;
const files = await fs.readdir(logsDir);
for (const file of files) {
const filePath = path.join(logsDir, file);
const stats = await fs.stat(filePath);
// Delete logs older than 7 days
if (Date.now() - stats.mtime.getTime() > 7 * 24 * 60 * 60 * 1000) {
await fs.unlink(filePath);
cleaned++;
}
}
return cleaned;
}
async cleanTempFiles() {
const fs = require('fs').promises;
const path = require('path');
const tempDir = path.join(think.ROOT_PATH, 'runtime/temp');
let cleaned = 0;
try {
const files = await fs.readdir(tempDir);
for (const file of files) {
const filePath = path.join(tempDir, file);
const stats = await fs.stat(filePath);
// Delete temp files older than 1 day
if (Date.now() - stats.mtime.getTime() > 24 * 60 * 60 * 1000) {
await fs.unlink(filePath);
cleaned++;
}
}
} catch (error) {
// Temp directory might not exist
}
return cleaned;
}
async cleanSoftDeletedRecords() {
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
let totalCleaned = 0;
// Clean soft-deleted users
const usersDeleted = await this.model('user')
.where({
status: 'deleted',
deleted_at: ['<', thirtyDaysAgo]
})
.delete();
totalCleaned += usersDeleted;
// Clean other soft-deleted records
// Add more models as needed
return totalCleaned;
}
};`,
'src/service/auth.js': `const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
module.exports = class extends think.Service {
constructor() {
super();
this.jwtSecret = think.config('jwt.secret') || 'your-secret-key';
this.jwtExpiry = think.config('jwt.expiry') || '1h';
this.refreshExpiry = think.config('jwt.refreshExpiry') || '7d';
}
/**
* Authenticate user with credentials
*/
async authenticate(email, password) {
// Find user by email
const user = await this.model('user').findByEmail(email);
if (think.isEmpty(user)) {
return { success: false, message: 'Invalid credentials' };
}
// Verify password
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
return { success: false, message: 'Invalid credentials' };
}
// Check if user is active
if (user.status !== 'active') {
return { success: false, message: 'Account is not active' };
}
// Generate tokens
const tokens = this.generateTokens(user);
// Update last login
await this.model('user').updateLastLogin(user.id);
// Remove sensitive data
delete user.password;
return {
success: true,
user,
tokens
};
}
/**
* Generate JWT tokens
*/
generateTokens(user) {
const payload = {
id: user.id,
email: user.email,
role: user.role
};
const accessToken = jwt.sign(payload, this.jwtSecret, {
expiresIn: this.jwtExpiry,
issuer: 'thinkjs-microservice'
});
const refreshToken = jwt.sign(
{ ...payload, type: 'refresh' },
this.jwtSecret,
{
expiresIn: this.refreshExpiry,
issuer: 'thinkjs-microservice'
}
);
return {
accessToken,
refreshToken,
expiresIn: this.getExpirySeconds(this.jwtExpiry)
};
}
/**
* Verify JWT token
*/
async verifyToken(token) {
try {
const decoded = jwt.verify(token, this.jwtSecret, {
issuer: 'thinkjs-microservice'
});
// Check if user still exists and is active
const user = await this.model('user')
.where({ id: decoded.id, status: 'active' })
.find();
if (think.isEmpty(user)) {
return { valid: false, message: 'User not found or inactive' };
}
delete user.password;
return {
valid: true,
user,
decoded
};
} catch (error) {
return {
valid: false,
message: error.message
};
}
}
/**
* Refresh access token
*/
async refreshToken(refreshToken) {
try {
const decoded = jwt.verify(refreshToken, this.jwtSecret, {
issuer: 'thinkjs-microservice'
});
if (decoded.type !== 'refresh') {
return { success: false, message: 'Invalid refresh token' };
}
// Get fresh user data
const user = await this.model('user')
.where({ id: decoded.id, status: 'active' })
.find();
if (think.isEmpty(user)) {
return { success: false, message: 'User not found or inactive' };
}
// Generate new tokens
const tokens = this.generateTokens(user);
return {
success: true,
tokens
};
} catch (error) {
return {
success: false,
message: error.message
};
}
}
/**
* Create API key for service authentication
*/
async createApiKey(userId, name, permissions = []) {
const apiKey = this.generateApiKey();
const hashedKey = await bcrypt.hash(apiKey, 10);
await this.model('api_key').add({
user_id: userId,
name,
key_hash: hashedKey,
permissions: JSON.stringify(permissions),
last_used_at: null,
created_at: new Date()
});
return apiKey;
}
/**
* Verify API key
*/
async verifyApiKey(apiKey) {
const keys = await this.model('api_key')
.where({ status: 'active' })
.select();
for (const key of keys) {
const isValid = await bcrypt.compare(apiKey, key.key_hash);
if (isValid) {
// Update last used
await this.model('api_key')
.where({ id: key.id })
.update({ last_used_at: new Date() });
return {
valid: true,
userId: key.user_id,
permissions: JSON.parse(key.permissions || '[]')
};
}
}
return { valid: false };
}
/**
* Generate random API key
*/
generateApiKey() {
const crypto = require('crypto');
return \`tk_\${crypto.randomBytes(32).toString('hex')}\`;
}
/**
* Convert expiry string to seconds
*/
getExpirySeconds(expiry) {
const units = {
s: 1,
m: 60,
h: 3600,
d: 86400
};
const match = expiry.match(/^(\\d+)([smhd])$/);
if (match) {
return parseInt(match[1]) * units[match[2]];
}
return 3600; // Default 1 hour
}
};`,
'src/config/extend.ts': `import { think } from 'thinkjs';
// Extend think with custom methods
export default {
// Add custom application methods
application: {
async getServiceHealth(): Promise<object> {
const checks = {
database: await this.checkDatabase(),
redis: await this.checkRedis(),
disk: await this.checkDiskSpace()
};
const healthy = Object.values(checks).every(check => check.status === 'healthy');
return {
status: healthy ? 'healthy' : 'unhealthy',
checks,
timestamp: new Date().toISOString()
};
},
async checkDatabase(): Promise<object> {
try {
await think.model('user').count();
return { status: 'healthy' };
} catch (error) {
return { status: 'unhealthy', error: error.message };
}
},
async checkRedis(): Promise<object> {
try {
const redis = think.cache('redis');
await redis.set('health_check', Date.now());
return { status: 'healthy' };
} catch (error) {
return { status: 'unhealthy', error: error.message };
}
},
async checkDiskSpace(): Promise<object> {
const os = require('os');
const disk = os.freemem() / os.totalmem();
return {
status: disk > 0.1 ? 'healthy' : 'unhealthy',
freePercent: Math.round(disk * 100)
};
}
},
// Add custom context methods
context: {
getUserFromToken(): object | null {
const token = this.header('authorization')?.replace('Bearer ', '');
if (!token) {
return null;
}
try {
const authService = think.service('auth');
const result = authService.verifyToken(token);
return result.valid ? result.user : null;
} catch (error) {
return null;
}
},
getRequestId(): string {
return this.header('x-request-id') || think.uuid('v4');
},
logRequest(level: string, message: string, data?: any): void {
const requestId = this.getRequestId();
const log = {
requestId,
method: this.method,
url: this.url,
ip: this.ip,
userAgent: this.header('user-agent'),
message,
data,
timestamp: new Date().toISOString()
};
think.logger[level](log);
}
},
// Add custom controller methods
controller: {
async cache(key: string, fn: Function, expire: number = 3600): Promise<any> {
const cache = await think.cache(key);
if (cache !== undefined) {
return cache;
}
const data = await fn();
await think.cache(key, data, expire);
return data;
},
async validateRequest(rules: object): Promise<boolean> {
const data = this.isGet ? this.get() : this.post();
const flag = await this.validate(rules, data);
if (!flag) {
this.fail(this.validateErrors);
return false;
}
return true;
}
}
};`,
'Dockerfile': `FROM node:18-alpine
# Install build dependencies
RUN apk add --no-cache python3 make g++
# Create app directory
WORKDIR /usr/src/app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy application files
COPY . .
# Build the application
RUN npm run build
# Create runtime directories
RUN mkdir -p runtime/cache runtime/session runtime/logs runtime/temp
# Set permissions
RUN chown -R node:node /usr/src/app
# Switch to non-root user
USER node
# Expose port
EXPOSE 8360
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD node -e "require('http').get('http://localhost:8360/health', (res) => process.exit(res.statusCode === 200 ? 0 : 1))"
# Start the application
CMD ["node", "production.js"]`,
'docker-compose.yml': `version: '3.8'
services:
app:
build: .
container_name: thinkjs-app
restart: unless-stopped
ports:
- "8360:8360"
environment:
- NODE_ENV=production
- PORT=8360
- DB_HOST=mysql
- DB_PORT=3306
- DB_NAME=thinkjs
- DB_USER=thinkjs
- DB_PASSWORD=thinkjs_password
- REDIS_HOST=redis
- REDIS_PORT=6379
depends_on:
- mysql
- redis
volumes:
- ./runtime:/usr/src/app/runtime
- ./logs:/usr/src/app/logs
networks:
- thinkjs-network
mysql:
image: mysql:8.0
container_name: thinkjs-mysql
restart: unless-stopped
environment:
- MYSQL_ROOT_PASSWORD=root_password
- MYSQL_DATABASE=thinkjs
- MYSQL_USER=thinkjs
- MYSQL_PASSWORD=thinkjs_password
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
networks:
- thinkjs-network
redis:
image: redis:7-alpine
container_name: thinkjs-redis
restart: unless-stopped
command: redis-server --appendonly yes
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- thinkjs-network
nginx:
image: nginx:alpine
container_name: thinkjs-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- app
networks:
- thinkjs-network
volumes:
mysql-data:
redis-data:
networks:
thinkjs-network:
driver: bridge`,
'README.md': `# ThinkJS Microservice
A modern Node.js microservice built with ThinkJS framework, featuring ES6/ES7 syntax, auto-loading, and comprehensive tooling.
## Features
- **ES6/ES7 Syntax**: Modern JavaScript with async/await throughout
- **MVC Architecture**: Auto-loading controllers, models, and services
- **ORM Support**: Built-in think-model with MySQL, PostgreSQL, SQLite adapters
- **RESTful APIs**: Automatic REST routing with validation
- **WebSocket**: Real-time communication with Socket.io
- **Caching**: Multiple cache adapters (File, Redis, Memcache)
- **I18n**: Built-in internationalization support
- **Cron Jobs**: Scheduled tasks with flexible configuration
- **TypeScript**: Optional TypeScript support
- **Docker**: Production-ready containerization
## Quick Start
\`\`\`bash
# Install dependencies
npm install
# Development mode with hot reload
npm run dev
# Build for production
npm run build
# Start production server
npm run production
# Run with Docker
docker-compose up -d
\`\`\`
## Project Structure
\`\`\`
├── src/
│ ├── config/ # Configuration files
│ │ ├── config.js # Base configuration
│ │ ├── adapter.js # Adapter configurations
│ │ ├── middleware.js
│ │ ├── router.js # Route definitions
│ │ └── crontab.js # Cron job schedules
│ ├── controller/ # Controllers (auto-loaded)
│ │ ├── api/ # API controllers
│ │ ├── websocket/ # WebSocket handlers
│ │ └── cron/ # Cron job controllers
│ ├── model/ # Models (auto-loaded)
│ ├── service/ # Business logic services
│ └── logic/ # Request validation logic
├── runtime/ # Runtime files (cache, logs, etc.)
├── view/ # View templates
└── www/ # Static assets
\`\`\`
## API Examples
### RESTful User API
\`\`\`bash
# Get all users
GET /api/v1/users?page=1&pageSize=20
# Get single user
GET /api/v1/users/123
# Create user
POST /api/v1/users
{
"username": "john_doe",
"email": "john@example.com",
"password": "secure_password"
}
# Update user
PUT /api/v1/users/123
{
"email": "newemail@example.com"
}
# Delete user
DELETE /api/v1/users/123
\`\`\`
### WebSocket
\`\`\`javascript
const socket = io('http://localhost:8360');
// Join chat
socket.emit('chat', {
message: 'Hello, ThinkJS!'
});
// Listen for messages
socket.on('message', (data) => {
console.log('New message:', data);
});
\`\`\`
## Configuration
### Database
Configure database in \`src/config/adapter.js\`:
\`\`\`javascript
exports.model = {
type: 'mysql',
mysql: {
database: 'thinkjs',
host: '127.0.0.1',
port: 3306,
user: 'root',
password: ''
}
};
\`\`\`
### Cache
Configure cache adapters:
\`\`\`javascript
exports.cache = {
type: 'redis',
redis: {
host: '127.0.0.1',
port: 6379
}
};
\`\`\`
## Testing
\`\`\`bash
# Run unit tests
npm test
# Run integration tests
npm run test-integration
# Generate coverage report
npm run coverage
\`\`\`
## Production Deployment
1. Build the application:
\`\`\`bash
npm run build
\`\`\`
2. Set environment variables:
\`\`\`bash
export NODE_ENV=production
export PORT=8360
export DB_HOST=your-db-host
\`\`\`
3. Start with PM2:
\`\`\`bash
pm2 start production.js -i max
\`\`\`
## Docker Deployment
\`\`\`bash
# Build and start all services
docker-compose up -d
# View logs
docker-compose logs -f app
# Scale application
docker-compose up -d --scale app=3
\`\`\`
## License
MIT`
}
};