update mariadb

This commit is contained in:
2026-03-01 14:42:49 +09:00
parent aa6667235a
commit c0b3598f88
4 changed files with 152 additions and 51 deletions

View File

@@ -28,9 +28,6 @@ COPY --from=build /app/dist ./dist
COPY server.js . COPY server.js .
COPY favicon.png . COPY favicon.png .
# 데이터베이스 디렉토리 생성
RUN mkdir -p db
# 환경변수 포트 노출 (Dokploy 등에서 PORT 주입 시 사용됨) # 환경변수 포트 노출 (Dokploy 등에서 PORT 주입 시 사용됨)
ENV PORT=80 ENV PORT=80
EXPOSE 80 EXPOSE 80

99
package-lock.json generated
View File

@@ -13,6 +13,7 @@
"express": "^5.2.1", "express": "^5.2.1",
"leaflet": "1.9.4", "leaflet": "1.9.4",
"lucide-react": "0.460.0", "lucide-react": "0.460.0",
"mysql2": "^3.18.2",
"qrcode.react": "^4.2.0", "qrcode.react": "^4.2.0",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
@@ -1459,6 +1460,7 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.9.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.9.tgz",
"integrity": "sha512-PD03/U8g1F9T9MI+1OBisaIARhSzeidsUjQaf51fOxrfjeiKN9bLVO06lHuHYjxdnqLWJijJHfqXPSJri2EM2A==", "integrity": "sha512-PD03/U8g1F9T9MI+1OBisaIARhSzeidsUjQaf51fOxrfjeiKN9bLVO06lHuHYjxdnqLWJijJHfqXPSJri2EM2A==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
} }
@@ -1586,6 +1588,15 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0" "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
} }
}, },
"node_modules/aws-ssl-profiles": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
"license": "MIT",
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -2130,6 +2141,15 @@
"license": "MIT", "license": "MIT",
"optional": true "optional": true
}, },
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10"
}
},
"node_modules/depd": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -2692,6 +2712,15 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/generate-function": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
"license": "MIT",
"dependencies": {
"is-property": "^1.0.2"
}
},
"node_modules/gensync": { "node_modules/gensync": {
"version": "1.0.0-beta.2", "version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -3062,6 +3091,12 @@
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
"license": "MIT"
},
"node_modules/isexe": { "node_modules/isexe": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -3169,6 +3204,21 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"node_modules/lru.min": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
"integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
"license": "MIT",
"engines": {
"bun": ">=1.0.0",
"deno": ">=1.30.0",
"node": ">=8.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wellwelwel"
}
},
"node_modules/lucide-react": { "node_modules/lucide-react": {
"version": "0.460.0", "version": "0.460.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.460.0.tgz", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.460.0.tgz",
@@ -3601,6 +3651,40 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/mysql2": {
"version": "3.18.2",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.18.2.tgz",
"integrity": "sha512-UfEShBFAZZEAKjySnTUuE7BgqkYT4mx+RjoJ5aqtmwSSvNcJ/QxQPXz/y3jSxNiVRedPfgccmuBtiPCSiEEytw==",
"license": "MIT",
"dependencies": {
"aws-ssl-profiles": "^1.1.2",
"denque": "^2.1.0",
"generate-function": "^2.3.1",
"iconv-lite": "^0.7.2",
"long": "^5.3.2",
"lru.min": "^1.1.4",
"named-placeholders": "^1.1.6",
"sql-escaper": "^1.3.3"
},
"engines": {
"node": ">= 8.0"
},
"peerDependencies": {
"@types/node": ">= 8"
}
},
"node_modules/named-placeholders": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
"integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
"license": "MIT",
"dependencies": {
"lru.min": "^1.1.0"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.11", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -4636,6 +4720,21 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/sql-escaper": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz",
"integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==",
"license": "MIT",
"engines": {
"bun": ">=1.0.0",
"deno": ">=2.0.0",
"node": ">=12.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/mysqljs/sql-escaper?sponsor=1"
}
},
"node_modules/sqlite": { "node_modules/sqlite": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/sqlite/-/sqlite-5.1.1.tgz", "resolved": "https://registry.npmjs.org/sqlite/-/sqlite-5.1.1.tgz",

View File

@@ -14,6 +14,7 @@
"express": "^5.2.1", "express": "^5.2.1",
"leaflet": "1.9.4", "leaflet": "1.9.4",
"lucide-react": "0.460.0", "lucide-react": "0.460.0",
"mysql2": "^3.18.2",
"qrcode.react": "^4.2.0", "qrcode.react": "^4.2.0",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4",

View File

@@ -1,10 +1,7 @@
import express from 'express'; import express from 'express';
import sqlite3 from 'sqlite3'; import mysql from 'mysql2/promise';
import cors from 'cors'; import cors from 'cors';
import { open } from 'sqlite';
import path from 'path'; import path from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
@@ -19,54 +16,56 @@ app.use(cors());
app.use(express.json()); app.use(express.json());
// Database setup // Database setup
let db; let pool;
async function initializeDB() { async function initializeDB() {
const dbDir = path.join(__dirname, 'db'); const dbConfig = {
if (!fs.existsSync(dbDir)) { host: process.env.DB_HOST || 'truenas.site',
fs.mkdirSync(dbDir, { recursive: true }); user: process.env.DB_USER || 'wifi',
} password: process.env.DB_PASSWORD || 'wifi12345',
database: process.env.DB_NAME || 'wifishare',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
};
const dbPath = path.join(dbDir, 'wifi_markers.db'); try {
db = await open({ pool = mysql.createPool(dbConfig);
filename: dbPath,
driver: sqlite3.Database
});
// Enable WAL mode for concurrency // Create table (MariaDB/MySQL syntax)
await db.exec('PRAGMA journal_mode = WAL;'); await pool.query(`
// Create table
await db.exec(`
CREATE TABLE IF NOT EXISTS markers ( CREATE TABLE IF NOT EXISTS markers (
id TEXT PRIMARY KEY, id VARCHAR(50) PRIMARY KEY,
name TEXT NOT NULL, name VARCHAR(255) NOT NULL,
ssid TEXT NOT NULL, ssid VARCHAR(255) NOT NULL,
password TEXT, password VARCHAR(255),
lat REAL NOT NULL, lat DOUBLE NOT NULL,
lng REAL NOT NULL, lng DOUBLE NOT NULL,
securityType TEXT NOT NULL, securityType VARCHAR(50) NOT NULL,
iconType TEXT NOT NULL, iconType VARCHAR(50) NOT NULL,
description TEXT, description TEXT,
addedBy TEXT, addedBy VARCHAR(100),
createdAt INTEGER, createdAt BIGINT,
isPublic INTEGER DEFAULT 1 isPublic TINYINT DEFAULT 1
) )
`); `);
console.log(`[DB] Database initialized at: ${dbPath}`); console.log(`[DB] MariaDB connected to ${dbConfig.host}/${dbConfig.database}`);
console.log('[DB] WAL mode enabled for concurrent writes.');
const count = await db.get('SELECT COUNT(*) as count FROM markers'); const [rows] = await pool.query('SELECT COUNT(*) as count FROM markers');
console.log(`[DB] Current Status: ${count.count} markers stored in database.`); console.log(`[DB] Current Status: ${rows[0].count} markers stored in database.`);
} catch (error) {
console.error('[DB] MariaDB connection failed:', error.message);
process.exit(1);
}
} }
// Routes // Routes
app.get('/api/markers', async (req, res) => { app.get('/api/markers', async (req, res) => {
const host = req.headers.host; const host = req.headers.host;
if (isDebug) console.log(`[API] GET /api/markers - Host: ${host} - Fetching from SQLite`); if (isDebug) console.log(`[API] GET /api/markers - Host: ${host} - Fetching from MariaDB`);
try { try {
const markers = await db.all('SELECT * FROM markers'); const [markers] = await pool.query('SELECT * FROM markers');
if (isDebug) console.log(`[API] Found ${markers.length} markers`); if (isDebug) console.log(`[API] Found ${markers.length} markers`);
// Convert isPublic from 0/1 to boolean // Convert isPublic from 0/1 to boolean
const formattedMarkers = markers.map(m => ({ const formattedMarkers = markers.map(m => ({
@@ -82,12 +81,17 @@ app.get('/api/markers', async (req, res) => {
app.post('/api/markers', async (req, res) => { app.post('/api/markers', async (req, res) => {
const { id, name, ssid, password, lat, lng, securityType, iconType, description, addedBy, createdAt, isPublic } = req.body; const { id, name, ssid, password, lat, lng, securityType, iconType, description, addedBy, createdAt, isPublic } = req.body;
if (isDebug) console.log(`[API] POST /api/markers - Saving marker: ${name} (${ssid}) to SQLite`); if (isDebug) console.log(`[API] POST /api/markers - Saving marker: ${name} (${ssid}) to MariaDB`);
try { try {
await db.run(` await pool.query(`
INSERT OR REPLACE INTO markers (id, name, ssid, password, lat, lng, securityType, iconType, description, addedBy, createdAt, isPublic) INSERT INTO markers (id, name, ssid, password, lat, lng, securityType, iconType, description, addedBy, createdAt, isPublic)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
name=VALUES(name), ssid=VALUES(ssid), password=VALUES(password),
lat=VALUES(lat), lng=VALUES(lng), securityType=VALUES(securityType),
iconType=VALUES(iconType), description=VALUES(description),
addedBy=VALUES(addedBy), createdAt=VALUES(createdAt), isPublic=VALUES(isPublic)
`, [id, name, ssid, password, lat, lng, securityType, iconType, description || '', addedBy, createdAt, isPublic ? 1 : 0]); `, [id, name, ssid, password, lat, lng, securityType, iconType, description || '', addedBy, createdAt, isPublic ? 1 : 0]);
if (isDebug) console.log(`[API] Successfully saved marker: ${id}`); if (isDebug) console.log(`[API] Successfully saved marker: ${id}`);
@@ -100,9 +104,9 @@ app.post('/api/markers', async (req, res) => {
app.delete('/api/markers/:id', async (req, res) => { app.delete('/api/markers/:id', async (req, res) => {
const { id } = req.params; const { id } = req.params;
if (isDebug) console.log(`[API] DELETE /api/markers/${id} - Deleting marker from SQLite`); if (isDebug) console.log(`[API] DELETE /api/markers/${id} - Deleting marker from MariaDB`);
try { try {
await db.run('DELETE FROM markers WHERE id = ?', [id]); await pool.query('DELETE FROM markers WHERE id = ?', [id]);
if (isDebug) console.log(`[API] Successfully deleted marker: ${id}`); if (isDebug) console.log(`[API] Successfully deleted marker: ${id}`);
res.json({ success: true }); res.json({ success: true });
} catch (error) { } catch (error) {