Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff7d4e4b9a | ||
|
|
c0c4cf3ab2 | ||
|
|
9f81c0d03e | ||
|
|
ead5ff0fdf | ||
|
|
2f210d3b34 | ||
|
|
0efe144b09 | ||
|
|
3aa442e0fa |
3
.gitignore
vendored
3
.gitignore
vendored
@ -40,6 +40,9 @@ Desktop.ini
|
||||
|
||||
# Project Specific - Server
|
||||
server/.env
|
||||
server/.env.backup*
|
||||
server/*.tmp
|
||||
server/backups/
|
||||
server/uploads/*
|
||||
!server/uploads/.gitkeep
|
||||
server/server.zip
|
||||
|
||||
23
backup/.env.backup.2026-01-26-00-58-22
Normal file
23
backup/.env.backup.2026-01-26-00-58-22
Normal file
@ -0,0 +1,23 @@
|
||||
# ==============================================
|
||||
# [Common Settings]
|
||||
# ==============================================
|
||||
DB_HOST=sokuree.com
|
||||
DB_USER=choibk
|
||||
DB_PASSWORD=^Ocean1472bk
|
||||
PORT=3005
|
||||
|
||||
# ==============================================
|
||||
# [Development Environment] - Local Windows
|
||||
# ==============================================
|
||||
# 로컬 개발용 DB (분리됨: sokuree_platform_dev)
|
||||
DB_NAME=sokuree_platform_dev
|
||||
DB_PORT=3307
|
||||
# Windows 환경 호환성 (tcp는 권한 오류 발생 가능)
|
||||
CCTV_TRANSPORT_OVERRIDE=auto
|
||||
|
||||
# ==============================================
|
||||
# [Production Environment] - Synology NAS
|
||||
# ==============================================
|
||||
# DB_NAME=sokuree_platform_prod
|
||||
# DB_PORT=3307
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "smartims",
|
||||
"version": "0.4.0.0",
|
||||
"version": "0.4.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "smartims",
|
||||
"version": "0.4.0.0",
|
||||
"version": "0.4.0.1",
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "smartims",
|
||||
"private": true,
|
||||
"version": "0.4.0.1",
|
||||
"version": "0.4.2.7",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@ -51,6 +51,9 @@ const sessionStoreOptions = {
|
||||
};
|
||||
|
||||
const sessionStore = new MySQLStore(sessionStoreOptions);
|
||||
sessionStore.on('error', (err) => {
|
||||
console.error('Session Store Error:', err);
|
||||
});
|
||||
|
||||
// Middleware
|
||||
app.use(cors({
|
||||
@ -443,7 +446,15 @@ const initTables = async () => {
|
||||
await db.query(insert, ['cctv', 'CCTV', false, null]);
|
||||
} else {
|
||||
// One-time update: Rename 'monitoring' code to 'cctv' (migration)
|
||||
await db.query("UPDATE system_modules SET code = 'cctv' WHERE code = 'monitoring'");
|
||||
// Use subquery or check if cctv exists to avoid ER_DUP_ENTRY
|
||||
const [cctvExists] = await db.query("SELECT 1 FROM system_modules WHERE code = 'cctv'");
|
||||
if (cctvExists.length > 0) {
|
||||
// If cctv already exists, just remove monitoring if it's there
|
||||
await db.query("DELETE FROM system_modules WHERE code = 'monitoring'");
|
||||
} else {
|
||||
// If cctv doesn't exist, try renaming monitoring
|
||||
await db.query("UPDATE system_modules SET code = 'cctv' WHERE code = 'monitoring'");
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ Tables Initialized');
|
||||
@ -456,24 +467,20 @@ initTables();
|
||||
const packageJson = require('./package.json');
|
||||
|
||||
app.get('/api/health', (req, res) => {
|
||||
// Force Korean time (UTC+9) for the timestamp
|
||||
// Dynamic version check (Light-weight)
|
||||
const kstOffset = 9 * 60 * 60 * 1000;
|
||||
const kstDate = new Date(Date.now() + kstOffset);
|
||||
|
||||
// Dynamic Version detection: Prioritize local Git Tag to match the "Update" rule
|
||||
let version = '0.0.0.0';
|
||||
let version = packageJson.version;
|
||||
try {
|
||||
const { execSync } = require('child_process');
|
||||
// Get the latest tag on the current commit
|
||||
version = execSync('git describe --tags --abbrev=0', { cwd: path.join(__dirname, '..') }).toString().trim().replace(/^v/, '');
|
||||
// Check git tag in parent directory (Project root)
|
||||
version = execSync('git describe --tags --abbrev=0', {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
stdio: ['ignore', 'pipe', 'ignore']
|
||||
}).toString().trim().replace(/^v/, '');
|
||||
} catch (e) {
|
||||
// Fallback to root package.json if git fails
|
||||
try {
|
||||
const rootPkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8'));
|
||||
version = rootPkg.version;
|
||||
} catch (err) {
|
||||
version = packageJson.version; // Fallback to server/package.json
|
||||
}
|
||||
// Safe fallback to package.json
|
||||
}
|
||||
|
||||
res.json({
|
||||
|
||||
4
server/package-lock.json
generated
4
server/package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "0.4.0.0",
|
||||
"version": "0.4.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "server",
|
||||
"version": "0.4.0.0",
|
||||
"version": "0.4.0.1",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^1.13.2",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "0.4.0.1",
|
||||
"version": "0.4.2.7",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@ -153,6 +153,20 @@ router.post('/test-db', isAuthenticated, hasRole('admin'), async (req, res) => {
|
||||
|
||||
let conn;
|
||||
try {
|
||||
// 1. Try to connect without specifying database first to see if credentials/host are OK
|
||||
try {
|
||||
const basicConn = await mysql.createConnection({
|
||||
host, user, password, port: parseInt(port) || 3306, connectTimeout: 3000
|
||||
});
|
||||
await basicConn.end();
|
||||
} catch (basicErr) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: `서버 접속 실패: 계정 정보나 호스트/포트를 확인하세요. (${basicErr.message})`
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Try to connect with database
|
||||
conn = await mysql.createConnection({
|
||||
host,
|
||||
user,
|
||||
@ -164,7 +178,13 @@ router.post('/test-db', isAuthenticated, hasRole('admin'), async (req, res) => {
|
||||
await conn.query('SELECT 1');
|
||||
res.json({ success: true, message: '연결 성공: 데이터베이스에 성공적으로 접속되었습니다.' });
|
||||
} catch (err) {
|
||||
res.status(400).json({ success: false, error: err.message });
|
||||
let msg = err.message;
|
||||
if (err.code === 'ER_BAD_DB_ERROR') {
|
||||
msg = `데이터베이스 '${database}'가 존재하지 않습니다. MariaDB에서 스키마를 먼저 생성해 주세요.`;
|
||||
} else if (err.code === 'ER_ACCESS_DENIED_ERROR') {
|
||||
msg = '사용자 계정 또는 비밀번호가 일치하지 않거나, 해당 DB에 대한 접근 권한이 없습니다.';
|
||||
}
|
||||
res.status(400).json({ success: false, error: msg });
|
||||
} finally {
|
||||
if (conn) await conn.end();
|
||||
}
|
||||
@ -469,33 +489,36 @@ const getGiteaAuth = async () => {
|
||||
// 5. Get Version Info (Current, Remote & History from Tags)
|
||||
router.get('/version/remote', isAuthenticated, hasRole('admin'), async (req, res) => {
|
||||
try {
|
||||
// Dynamic Version detection: Prioritize local Git Tag
|
||||
// Local version detection (Dynamic & Robust)
|
||||
const projectRoot = path.join(__dirname, '../..');
|
||||
let currentVersion = '0.0.0.0';
|
||||
try {
|
||||
currentVersion = execSync('git describe --tags --abbrev=0', { cwd: path.join(__dirname, '../..') }).toString().trim().replace(/^v/, '');
|
||||
const { execSync } = require('child_process');
|
||||
currentVersion = execSync('git describe --tags --abbrev=0', {
|
||||
cwd: projectRoot,
|
||||
stdio: ['ignore', 'pipe', 'ignore']
|
||||
}).toString().trim().replace(/^v/, '');
|
||||
} catch (e) {
|
||||
try {
|
||||
const packageJsonPath = path.resolve(__dirname, '../../package.json');
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||
currentVersion = packageJson.version;
|
||||
const rootPkg = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'));
|
||||
currentVersion = rootPkg.version;
|
||||
} catch (err) {
|
||||
currentVersion = '0.4.0.0'; // Ultimate fallback
|
||||
currentVersion = '0.4.2.7';
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare git fetch command with auth if available
|
||||
// Prepare git fetch command
|
||||
const auth = await getGiteaAuth();
|
||||
let fetchCmd = 'git fetch --tags --force';
|
||||
let fetchCmd = 'git fetch --tags';
|
||||
|
||||
if (auth.user && auth.pass) {
|
||||
if (auth.user && auth.pass && auth.url && auth.url.includes('https://')) {
|
||||
const authenticatedUrl = auth.url.replace('https://', `https://${encodeURIComponent(auth.user)}:${encodeURIComponent(auth.pass)}@`);
|
||||
// Use explicit refspec to ensure local tags are updated from the remote URL
|
||||
fetchCmd = `git fetch ${authenticatedUrl} +refs/tags/*:refs/tags/* --force --prune --prune-tags`;
|
||||
} else {
|
||||
fetchCmd = `git fetch ${auth.url} +refs/tags/*:refs/tags/* --force --prune --prune-tags`;
|
||||
fetchCmd = `git fetch ${authenticatedUrl} --tags --force --prune`;
|
||||
} else if (auth.url) {
|
||||
fetchCmd = `git fetch ${auth.url} --tags --force --prune`;
|
||||
}
|
||||
|
||||
exec(fetchCmd, (err, stdout, stderr) => {
|
||||
exec(fetchCmd, { cwd: projectRoot }, (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
console.error('Git fetch failed:', err);
|
||||
const sanitizedError = stderr.replace(/:[^@]+@/g, ':****@');
|
||||
@ -512,7 +535,7 @@ router.get('/version/remote', isAuthenticated, hasRole('admin'), async (req, res
|
||||
const format = '%(refname:short)|%(contents:subject)|%(contents:body)|%(creatordate:iso8601)';
|
||||
const historyCmd = `git for-each-ref refs/tags --sort=-creatordate --format="${format}" --count=50`;
|
||||
|
||||
exec(historyCmd, (err, stdout, stderr) => {
|
||||
exec(historyCmd, { cwd: projectRoot }, (err, stdout, stderr) => {
|
||||
const lines = stdout ? stdout.trim().split('\n') : [];
|
||||
const allTags = lines.map(line => {
|
||||
const [tag, subject, body, date] = line.split('|');
|
||||
@ -599,7 +622,7 @@ router.post('/version/update', isAuthenticated, hasRole('admin'), async (req, re
|
||||
|
||||
// Build auth URL for git commands
|
||||
let remoteUrl = auth.url;
|
||||
if (auth.user && auth.pass) {
|
||||
if (auth.user && auth.pass && auth.url.includes('https://')) {
|
||||
remoteUrl = auth.url.replace('https://', `https://${encodeURIComponent(auth.user)}:${encodeURIComponent(auth.pass)}@`);
|
||||
}
|
||||
|
||||
@ -618,18 +641,33 @@ router.post('/version/update', isAuthenticated, hasRole('admin'), async (req, re
|
||||
scriptContent = `
|
||||
@echo off
|
||||
echo [Update] Starting update to ${targetTag}...
|
||||
if not exist "${backupDir}" mkdir "${backupDir}"
|
||||
|
||||
echo [Update] Backing up Uploads & Config...
|
||||
tar -czvf "${backupDir}/backup_images_${timestamp}.tar.gz" server/uploads/
|
||||
if exist "server\\.env" copy "server\\.env" "${backupDir}\\env_backup_${timestamp}"
|
||||
REM Ensure backup directory
|
||||
set BACKUP_PATH=${backupDir}
|
||||
if not exist "%BACKUP_PATH%" mkdir "%BACKUP_PATH%" 2>nul
|
||||
if not exist "%BACKUP_PATH%" (
|
||||
echo [Warning] Global backup failed, using local backup.
|
||||
set BACKUP_PATH=.\\server\\backups
|
||||
if not exist ".\\server\\backups" mkdir ".\\server\\backups"
|
||||
)
|
||||
|
||||
echo [Update] Backing up Config...
|
||||
if exist "server\\.env" (
|
||||
copy /Y "server\\.env" "%BACKUP_PATH%\\.env.backup.${timestamp}"
|
||||
copy /Y "server\\.env" "server\\.env.tmp"
|
||||
)
|
||||
|
||||
echo [Update] Syncing Source Code...
|
||||
git fetch "${remoteUrl}" +refs/tags/*:refs/tags/* --force --prune --prune-tags
|
||||
git fetch "${remoteUrl}" --tags --force --prune
|
||||
git checkout -f ${targetTag}
|
||||
|
||||
echo [Update] Restoring Config...
|
||||
if exist "${backupDir}\\env_backup_${timestamp}" copy /Y "${backupDir}\\env_backup_${timestamp}" "server\\.env"
|
||||
if exist "server\\.env.tmp" (
|
||||
copy /Y "server\\.env.tmp" "server\\.env"
|
||||
del "server\\.env.tmp"
|
||||
) else if exist "%BACKUP_PATH%\\.env.backup.${timestamp}" (
|
||||
copy /Y "%BACKUP_PATH%\\.env.backup.${timestamp}" "server\\.env"
|
||||
)
|
||||
|
||||
echo [Update] Installing & Building...
|
||||
call npm install
|
||||
@ -637,8 +675,7 @@ call npm run build
|
||||
cd server
|
||||
call npm install
|
||||
|
||||
echo [Update] Restarting Server...
|
||||
echo "Please restart your dev server manually if needed."
|
||||
echo [Update] Done.
|
||||
`;
|
||||
} else {
|
||||
// Linux/Synology Script
|
||||
@ -646,22 +683,59 @@ echo "Please restart your dev server manually if needed."
|
||||
scriptContent = `#!/bin/bash
|
||||
exec > >(tee -a update.log) 2>&1
|
||||
echo "[Update] Starting update to ${targetTag}..."
|
||||
mkdir -p ${backupDir}
|
||||
|
||||
# Ensure backup directory
|
||||
BACKUP_DIR="${backupDir}"
|
||||
mkdir -p "$BACKUP_DIR" || {
|
||||
echo "[Warning] Global backup failed, using local backup."
|
||||
BACKUP_DIR="./server/backups"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
}
|
||||
|
||||
echo "[Update] Backing up Database..."
|
||||
${dumpTool} -u ${dbUser} --password='${dbPass}' --port ${dbPort} ${dbName} > ${backupDir}/backup_db_${timestamp}.sql || echo "DB Backup Failed, continuing..."
|
||||
if [ -f "${dumpTool}" ]; then
|
||||
echo "[Info] MySQL dump tool found. Attempting database backup..."
|
||||
${dumpTool} -u ${dbUser} --password='${dbPass}' --port ${dbPort} ${dbName} > "$BACKUP_DIR/backup_db_${timestamp}.sql" 2>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "[Info] Database backup successful."
|
||||
else
|
||||
echo "[Warning] Database backup failed, continuing..."
|
||||
fi
|
||||
else
|
||||
echo "[Warning] MySQL dump tool not found. Skipping DB backup."
|
||||
fi
|
||||
|
||||
echo "[Update] Backing up Uploads & Config..."
|
||||
tar -czvf ${backupDir}/backup_images_${timestamp}.tar.gz server/uploads/
|
||||
cp server/.env ${backupDir}/.env.backup.${timestamp}
|
||||
echo "[Update] Backing up Config..."
|
||||
if [ -f "server/.env" ]; then
|
||||
echo "[Info] Backing up 'server/.env' to '$BACKUP_DIR/.env.backup.${timestamp}' and 'server/.env.tmp'."
|
||||
cp "server/.env" "$BACKUP_DIR/.env.backup.${timestamp}"
|
||||
cp "server/.env" "server/.env.tmp"
|
||||
else
|
||||
echo "[Warning] 'server/.env' not found. Skipping config backup."
|
||||
fi
|
||||
|
||||
echo "[Update] Syncing Source Code..."
|
||||
git remote set-url origin "${remoteUrl}"
|
||||
git fetch origin +refs/tags/*:refs/tags/* --force --prune --prune-tags
|
||||
git fetch origin --tags --force --prune
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "[Error] Git fetch failed. Exiting update."
|
||||
exit 1
|
||||
fi
|
||||
echo "[Info] Git fetch successful."
|
||||
git checkout -f ${targetTag}
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "[Error] Git checkout to ${targetTag} failed. Exiting update."
|
||||
exit 1
|
||||
fi
|
||||
echo "[Info] Git checkout to ${targetTag} successful."
|
||||
|
||||
echo "[Update] Restoring Config..."
|
||||
cp ${backupDir}/.env.backup.${timestamp} server/.env
|
||||
if [ -f "server/.env.tmp" ]; then
|
||||
cp "server/.env.tmp" "server/.env"
|
||||
rm "server/.env.tmp"
|
||||
elif [ -f "$BACKUP_DIR/.env.backup.${timestamp}" ]; then
|
||||
cp "$BACKUP_DIR/.env.backup.${timestamp}" "server/.env"
|
||||
fi
|
||||
|
||||
echo "[Update] Installing & Building..."
|
||||
npm install
|
||||
|
||||
@ -151,7 +151,11 @@ export function BasicSettingsPage() {
|
||||
setTestResult({ success: true, message: res.data.message });
|
||||
setIsDbVerified(true);
|
||||
} catch (error: any) {
|
||||
setTestResult({ success: false, message: error.response?.data?.error || '접속 테스트에 실패했습니다.' });
|
||||
const detailedError = error.response?.data?.error || error.message || '알 수 없는 오류가 발생했습니다.';
|
||||
setTestResult({
|
||||
success: false,
|
||||
message: `접속 실패: ${detailedError}`
|
||||
});
|
||||
setIsDbVerified(false);
|
||||
} finally {
|
||||
setTesting(false);
|
||||
@ -329,7 +333,7 @@ export function BasicSettingsPage() {
|
||||
|
||||
{/* Section 2.5: Gitea Repository Update Settings */}
|
||||
<Card className="overflow-hidden border-slate-200 shadow-sm">
|
||||
<div className="p-6 border-b border-slate-50 bg-slate-50/50">
|
||||
<div className="p-6 border-b border-slate-50 bg-slate-50/50 flex justify-between items-center">
|
||||
<h2 className="text-lg font-bold text-slate-800 flex items-center gap-2">
|
||||
<RefreshCcw size={20} className="text-emerald-600" />
|
||||
시스템 업데이트 저장소 설정 (Gitea)
|
||||
|
||||
39
update_system.bat
Normal file
39
update_system.bat
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
@echo off
|
||||
echo [Update] Starting update to v0.4.2.6...
|
||||
|
||||
REM Ensure backup directory
|
||||
set BACKUP_PATH=./backup
|
||||
if not exist "%BACKUP_PATH%" mkdir "%BACKUP_PATH%" 2>nul
|
||||
if not exist "%BACKUP_PATH%" (
|
||||
echo [Warning] Global backup failed, using local backup.
|
||||
set BACKUP_PATH=.\server\backups
|
||||
if not exist ".\server\backups" mkdir ".\server\backups"
|
||||
)
|
||||
|
||||
echo [Update] Backing up Config...
|
||||
if exist "server\.env" (
|
||||
copy /Y "server\.env" "%BACKUP_PATH%\.env.backup.2026-01-26-00-58-22"
|
||||
copy /Y "server\.env" "server\.env.tmp"
|
||||
)
|
||||
|
||||
echo [Update] Syncing Source Code...
|
||||
git fetch "https://gitea.qideun.com/SOKUREE/smart_ims.git" --tags --force --prune
|
||||
git checkout -f v0.4.2.6
|
||||
|
||||
echo [Update] Restoring Config...
|
||||
if exist "server\.env.tmp" (
|
||||
copy /Y "server\.env.tmp" "server\.env"
|
||||
del "server\.env.tmp"
|
||||
) else if exist "%BACKUP_PATH%\.env.backup.2026-01-26-00-58-22" (
|
||||
copy /Y "%BACKUP_PATH%\.env.backup.2026-01-26-00-58-22" "server\.env"
|
||||
)
|
||||
|
||||
echo [Update] Installing & Building...
|
||||
call npm install
|
||||
call npm run build
|
||||
cd server
|
||||
call npm install
|
||||
|
||||
echo [Update] Done.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user