feat: Add IP address and browser tracking

- Add database migration for ip_address and browser_info columns
- Install ua-parser-js for browser detection
- Add IP extraction helper function
- Update POST /api/dokumentasi to capture client IP and browser
- Update GET endpoints to return tracking data
- Display IP and browser info in documentation detail view
- Update server port to 3002

Features:
- Automatic IP address capture from request headers
- Browser and OS detection with version info
- Display in detail modal for each documentation
This commit is contained in:
2026-01-19 13:52:34 +08:00
parent d259088b71
commit 61189ba66b
5 changed files with 157 additions and 13 deletions

View File

@@ -2,6 +2,7 @@ require('dotenv').config();
const express = require('express');
const mysql = require('mysql2/promise');
const cors = require('cors');
const UAParser = require('ua-parser-js');
const app = express();
const PORT = process.env.SERVER_PORT || 3000;
@@ -36,6 +37,26 @@ async function testConnection() {
}
}
// ============================================
// HELPER FUNCTIONS
// ============================================
// Get client IP address
function getClientIp(req) {
return req.headers['x-forwarded-for']?.split(',')[0].trim() ||
req.headers['x-real-ip'] ||
req.socket.remoteAddress ||
req.connection.remoteAddress ||
'unknown';
}
// Parse user agent
function parseUserAgent(userAgentString) {
const parser = new UAParser(userAgentString);
const result = parser.getResult();
return `${result.browser.name || 'Unknown'} ${result.browser.version || ''} on ${result.os.name || 'Unknown'} ${result.os.version || ''}`;
}
// ============================================
// API ENDPOINTS
// ============================================
@@ -58,16 +79,20 @@ app.post('/api/dokumentasi', async (req, res) => {
});
}
// Get IP and browser info
const ipAddress = getClientIp(req);
const browserInfo = parseUserAgent(req.headers['user-agent'] || '');
const [result] = await pool.execute(
`INSERT INTO dokumentasi (nama, no_anggota, jenis_perjanjian, tanggal, catatan, foto)
VALUES (?, ?, ?, ?, ?, ?)`,
[nama, noAnggota, jenisPerjanjian, tanggal, catatan || null, foto]
`INSERT INTO dokumentasi (nama, no_anggota, jenis_perjanjian, tanggal, catatan, foto, ip_address, browser_info)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
[nama, noAnggota, jenisPerjanjian, tanggal, catatan || null, foto, ipAddress, browserInfo]
);
// Log activity
await pool.execute(
`INSERT INTO activity_log (action, dokumentasi_id, details) VALUES (?, ?, ?)`,
['CREATE', result.insertId, `Created documentation for ${nama}`]
['CREATE', result.insertId, `Created documentation for ${nama} from ${ipAddress}`]
);
res.status(201).json({
@@ -89,7 +114,7 @@ app.get('/api/dokumentasi', async (req, res) => {
try {
const { search } = req.query;
let query = 'SELECT id, nama, no_anggota as noAnggota, jenis_perjanjian as jenisPerjanjian, tanggal, catatan, foto, timestamp FROM dokumentasi';
let query = 'SELECT id, nama, no_anggota as noAnggota, jenis_perjanjian as jenisPerjanjian, tanggal, catatan, foto, timestamp, ip_address as ipAddress, browser_info as browserInfo FROM dokumentasi';
let params = [];
if (search) {
@@ -122,7 +147,7 @@ app.get('/api/dokumentasi/:id', async (req, res) => {
const [rows] = await pool.execute(
`SELECT id, nama, no_anggota as noAnggota, jenis_perjanjian as jenisPerjanjian,
tanggal, catatan, foto, timestamp
tanggal, catatan, foto, timestamp, ip_address as ipAddress, browser_info as browserInfo
FROM dokumentasi WHERE id = ?`,
[id]
);