138 lines
5.2 KiB
TypeScript

import 'dotenv/config'
import path from 'path'
import express from 'express'
import cors from 'cors'
import session from 'express-session'
import equipmentRoutes from './routes/equipment'
import borrowRoutes, { returnRouter } from './routes/borrow'
import statisticsRoutes from './routes/statistics'
import authRoutes from './routes/auth'
import barcodeRoutes from './routes/barcode'
import settingsRoutes from './routes/settings'
import usersRoutes from './routes/users'
import personRoutes from './routes/persons'
import storageLocationRoutes from './routes/storageLocations'
import assetTypeRoutes from './routes/assetTypes'
import departmentRoutes from './routes/departments'
import operationLogRoutes from './routes/operationLog'
import batchImportRoutes from './routes/batchImport'
import mountRoutes from './routes/mount'
import disposalRoutes from './routes/disposal'
import maintenanceRoutes from './routes/maintenance'
import batchRoutes from './routes/batch'
import exportRoutes from './routes/export'
import searchRoutes from './routes/search'
import syncRoutes from './routes/sync'
import notificationRoutes from './routes/notifications'
import inventoryRoutes from './routes/inventory'
import publicInventoryRoutes from './routes/publicInventory'
import printTerminalRoutes from './routes/printTerminals'
import printJobRoutes from './routes/printJobs'
import printAgentRoutes from './routes/printAgent'
import printDownloadRoutes from './routes/printDownloads'
import myAssetRoutes from './routes/myAssets'
import dingtalkRoutes from './routes/dingtalk'
import { keycloak, memoryStore } from './utils/keycloak'
import { protect } from './middleware/auth'
import { startHrSyncScheduler } from './services/hrSyncService'
import { ensurePublicAssetPersonExists } from './services/systemReservedPersonService'
import { startDingTalkDeliveryWorker } from './services/dingtalkDeliveryService'
import { isCorsOriginAllowed } from './utils/corsPolicy'
import { unsafeOriginGuard } from './middleware/originGuard'
import { assertSupportedAuthMode, resolveSessionSecret } from './utils/authMode'
const app = express()
const PORT = process.env.PORT || 3000
const AUTH_MODE = assertSupportedAuthMode()
const SESSION_SECRET = resolveSessionSecret()
const SESSION_COOKIE_SECURE = process.env.SESSION_COOKIE_SECURE === undefined
? process.env.NODE_ENV === 'production'
: process.env.SESSION_COOKIE_SECURE === 'true'
// Middleware
app.set('trust proxy', 1)
app.use(cors({
origin: (origin, callback) => callback(null, isCorsOriginAllowed(origin) ? (origin || true) : false),
credentials: true
}))
app.use(unsafeOriginGuard)
app.use(express.json({ limit: '50mb' }))
app.use(express.urlencoded({ extended: true, limit: '50mb' }))
app.use(
session({
secret: SESSION_SECRET,
resave: false,
saveUninitialized: false,
store: memoryStore,
cookie: {
secure: SESSION_COOKIE_SECURE,
httpOnly: true,
sameSite: 'lax',
maxAge: 1000 * 60 * 60 // 1 hour
}
})
)
if (AUTH_MODE === 'keycloak') {
app.use(keycloak.middleware({ logout: '/api/auth/logout', admin: '/keycloak' }))
}
app.use('/uploads', express.static(path.resolve(__dirname, '..', 'uploads')))
// Routes
app.use('/api/equipment', protect(), equipmentRoutes)
app.use('/api/borrow', protect(), borrowRoutes)
app.use('/api/return', protect(), returnRouter)
app.use('/api/statistics', protect(), statisticsRoutes)
app.use('/api/barcode', protect(), barcodeRoutes)
app.use('/api/auth', authRoutes)
app.use('/api/settings', settingsRoutes)
app.use('/api/users', protect(), usersRoutes)
app.use('/api/persons', personRoutes)
app.use('/api/storage-locations', storageLocationRoutes)
app.use('/api/asset-types', assetTypeRoutes)
app.use('/api/departments', departmentRoutes)
app.use('/api/operation-logs', operationLogRoutes)
app.use('/api/batch-import', batchImportRoutes)
app.use('/api/mount', mountRoutes)
app.use('/api/disposal', disposalRoutes)
app.use('/api/maintenance', maintenanceRoutes)
app.use('/api/batch', protect(), batchRoutes)
app.use('/api/export', protect(), exportRoutes)
app.use('/api/search', protect(), searchRoutes)
app.use('/api/sync', syncRoutes)
app.use('/api/notifications', notificationRoutes)
app.use('/api/inventory', inventoryRoutes)
app.use('/api/public/inventory', publicInventoryRoutes)
app.use('/api/print-terminals', protect(), printTerminalRoutes)
app.use('/api/print-jobs', protect(), printJobRoutes)
app.use('/api/print-agent', printAgentRoutes)
app.use('/api/my-assets', myAssetRoutes)
app.use('/api/dingtalk', dingtalkRoutes)
app.use('/downloads', printDownloadRoutes)
// Health check
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', message: 'Server is running' })
})
// Error handling middleware
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
console.error('Error:', err)
res.status(err.status || 500).json({
code: err.status || 500,
message: err.message || 'Internal Server Error',
data: null
})
})
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`)
})
ensurePublicAssetPersonExists().catch((error) => {
console.error('初始化系统保留人员失败:', error)
})
startHrSyncScheduler()
startDingTalkDeliveryWorker()