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",

100
server.js
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
};
try {
pool = mysql.createPool(dbConfig);
// Create table (MariaDB/MySQL syntax)
await pool.query(`
CREATE TABLE IF NOT EXISTS markers (
id VARCHAR(50) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
ssid VARCHAR(255) NOT NULL,
password VARCHAR(255),
lat DOUBLE NOT NULL,
lng DOUBLE NOT NULL,
securityType VARCHAR(50) NOT NULL,
iconType VARCHAR(50) NOT NULL,
description TEXT,
addedBy VARCHAR(100),
createdAt BIGINT,
isPublic TINYINT DEFAULT 1
)
`);
console.log(`[DB] MariaDB connected to ${dbConfig.host}/${dbConfig.database}`);
const [rows] = await pool.query('SELECT COUNT(*) as count FROM markers');
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);
} }
const dbPath = path.join(dbDir, 'wifi_markers.db');
db = await open({
filename: dbPath,
driver: sqlite3.Database
});
// Enable WAL mode for concurrency
await db.exec('PRAGMA journal_mode = WAL;');
// Create table
await db.exec(`
CREATE TABLE IF NOT EXISTS markers (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
ssid TEXT NOT NULL,
password TEXT,
lat REAL NOT NULL,
lng REAL NOT NULL,
securityType TEXT NOT NULL,
iconType TEXT NOT NULL,
description TEXT,
addedBy TEXT,
createdAt INTEGER,
isPublic INTEGER DEFAULT 1
)
`);
console.log(`[DB] Database initialized at: ${dbPath}`);
console.log('[DB] WAL mode enabled for concurrent writes.');
const count = await db.get('SELECT COUNT(*) as count FROM markers');
console.log(`[DB] Current Status: ${count.count} markers stored in database.`);
} }
// 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) {