Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/devfactory-homepage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
cat > .env <<'EOF'
APP_HOST=${{ vars.APP_HOST }}
DATABASE_URL=${{ secrets.DATABASE_URL }}
ACCESS_LOGGING_IP_SALT=${{ secrets.ACCESS_LOGGING_IP_SALT }}
EOF

- name: Build & up (prod)
Expand Down
3 changes: 3 additions & 0 deletions platform/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ APP_HOST=your-domain.com

# Database Setting
DATABASE_URL=postgresql://user:pass@devfactory-postgres:5432/dbname

# Logging Setting
ACCESS_LOGGING_IP_SALT=your-secret-salt-here
1 change: 1 addition & 0 deletions platform/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ services:
restart: unless-stopped
environment:
- DATABASE_URL=${DATABASE_URL}
- ACCESS_LOGGING_IP_SALT=${ACCESS_LOGGING_IP_SALT}
- PORT=3000
networks:
- traefik
Expand Down
39 changes: 36 additions & 3 deletions platform/server/src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require('dotenv').config();
const express = require('express');
const crypto = require('crypto');
const { Pool } = require('pg');
const cors = require('cors');

Expand All @@ -25,6 +26,35 @@ pool.query('SELECT NOW()', (err, res) => {
});

// API Routes

/**
* Extracts the client IP address from request headers or connection info.
*/
function getClientIp(req) {
// Check X-Forwarded-For header (common for reverse proxies)
const forwardedFor = req.headers['x-forwarded-for'];
if (forwardedFor) {
// Can be a comma-separated list; the first one is the original client
return forwardedFor.split(',')[0].trim();
}

// Check X-Real-IP header
const realIp = req.headers['x-real-ip'];
if (realIp) {
return realIp;
}

// Fallback to Express req.ip or socket address
return req.ip || req.socket.remoteAddress;
}

/**
* Hashes the IP address with a salt, matching the behavior in the cert system.
*/
function hashIp(ip, salt = '') {
if (!ip) return null;
return crypto.createHash('sha256').update(salt + ip).digest('hex');
}
app.get('/api/health', (req, res) => {
res.json({ status: 'ok' });
});
Expand All @@ -33,12 +63,15 @@ app.get('/api/health', (req, res) => {
app.post('/api/stats/visit', async (req, res) => {
try {
const { path, userAgent } = req.body;
// ๊ธฐ์กด ๋กœ๊ทธ ํฌ๋งท์— ๋งž์ถฐ method๋Š” 'PAGEVIEW'๋กœ, referrer๋Š” ํ˜„์žฌ ํ˜ธ์ŠคํŠธ๋กœ ๊ธฐ๋ก
const referrer = req.headers.referer || '';

// Extract client IP and generate hash
const clientIp = getClientIp(req);
const ipHash = hashIp(clientIp, process.env.ACCESS_LOGGING_IP_SALT || '');

await pool.query(
'INSERT INTO logging.access_log (path, method, status, user_agent, referrer, ts) VALUES ($1, $2, $3, $4, $5, NOW())',
[path || '/', 'PAGEVIEW', 200, userAgent, referrer]
'INSERT INTO logging.access_log (path, method, status, ip_hash, user_agent, referrer, ts) VALUES ($1, $2, $3, $4, $5, $6, NOW())',
[path || '/', 'PAGEVIEW', 200, ipHash, userAgent, referrer]
);
res.status(201).json({ message: 'Visit logged successfully' });
} catch (err) {
Expand Down
Loading