191 lines
6.2 KiB
JavaScript
191 lines
6.2 KiB
JavaScript
const express = require('express');
|
|
const cors = require('cors');
|
|
const { Pool } = require('pg');
|
|
const { createClient } = require('redis');
|
|
const cookieParser = require('cookie-parser');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
require('dotenv').config();
|
|
|
|
const app = express();
|
|
const port = process.env.PORT || 4001;
|
|
|
|
const allowedOrigins = [
|
|
'http://localhost:4000',
|
|
'https://qr.houseofwebsites.ai',
|
|
process.env.FRONTEND_URL
|
|
].filter(Boolean);
|
|
|
|
app.use(cors({ origin: allowedOrigins, credentials: true }));
|
|
app.use(express.json({ limit: '50mb' }));
|
|
app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
|
app.use(cookieParser());
|
|
|
|
// Routes
|
|
const authRoutes = require('./routes/auth');
|
|
const qrRoutes = require('./routes/qr');
|
|
const redirectRoutes = require('./routes/redirect');
|
|
const analyticsRoutes = require('./routes/analytics');
|
|
const adminRoutes = require('./routes/admin');
|
|
const paymentsRoutes = require('./routes/payments');
|
|
const foldersRoutes = require('./routes/folders');
|
|
|
|
app.use('/api/auth', authRoutes);
|
|
app.use('/api/qr', qrRoutes);
|
|
app.use('/api/analytics', analyticsRoutes);
|
|
app.use('/api/admin', adminRoutes);
|
|
app.use('/api/payments', paymentsRoutes);
|
|
app.use('/api/folders', foldersRoutes);
|
|
|
|
// Support Traefik stripped prefixes
|
|
app.use('/auth', authRoutes);
|
|
app.use('/qr', qrRoutes);
|
|
app.use('/analytics', analyticsRoutes);
|
|
app.use('/admin', adminRoutes);
|
|
app.use('/payments', paymentsRoutes);
|
|
app.use('/folders', foldersRoutes);
|
|
|
|
app.use('/l', redirectRoutes);
|
|
|
|
// Postgres setup
|
|
const pgPool = new Pool({
|
|
host: process.env.PGHOST || 'postgres',
|
|
port: process.env.PGPORT || 5432,
|
|
user: process.env.PGUSER || 'postgres',
|
|
password: process.env.PGPASSWORD || 'postgres',
|
|
database: process.env.PGDATABASE || 'ckqr',
|
|
});
|
|
|
|
// Initialize Database automatically
|
|
const initDb = async () => {
|
|
try {
|
|
const sql = fs.readFileSync(path.join(__dirname, 'db', 'init.sql'), 'utf8');
|
|
await pgPool.query(sql);
|
|
console.log('Database initialized successfully.');
|
|
|
|
// Force promote the owner to admin (enterprise role)
|
|
await pgPool.query("UPDATE users SET role = 'enterprise', is_email_verified = true WHERE email = 'mohan.k@cavininfotech.com'");
|
|
await pgPool.query("UPDATE users SET role = 'enterprise', is_email_verified = true WHERE email = 'admin@cavininfotech.com'");
|
|
await pgPool.query("UPDATE users SET role = 'enterprise', is_email_verified = true WHERE role = 'admin'");
|
|
} catch (err) {
|
|
console.error('Database initialization failed:', err);
|
|
}
|
|
};
|
|
initDb();
|
|
|
|
// Redis setup
|
|
const redisClient = createClient({
|
|
url: `redis://${process.env.REDIS_HOST || 'redis'}:${process.env.REDIS_PORT || 6379}`
|
|
});
|
|
|
|
redisClient.on('error', (err) => console.log('Redis Client Error', err));
|
|
|
|
const healthCheck = async (req, res) => {
|
|
let pgStatus = 'Unknown';
|
|
let redisStatus = 'Unknown';
|
|
|
|
try {
|
|
const pgRes = await pgPool.query('SELECT NOW()');
|
|
pgStatus = 'Connected';
|
|
} catch (err) {
|
|
pgStatus = `Error: ${err.message}`;
|
|
}
|
|
|
|
try {
|
|
if (!redisClient.isOpen) {
|
|
await redisClient.connect();
|
|
}
|
|
await redisClient.ping();
|
|
redisStatus = 'Connected';
|
|
} catch (err) {
|
|
redisStatus = `Error: ${err.message}`;
|
|
}
|
|
|
|
res.json({
|
|
status: 'ok',
|
|
services: {
|
|
api: 'Running',
|
|
postgres: pgStatus,
|
|
redis: redisStatus
|
|
}
|
|
});
|
|
};
|
|
|
|
app.get('/api/health', healthCheck);
|
|
app.get('/health', healthCheck);
|
|
|
|
// Auto-migrate schema
|
|
pgPool.query(`
|
|
CREATE TABLE IF NOT EXISTS folders (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
|
name VARCHAR(255) NOT NULL,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);`).catch(console.error);
|
|
|
|
pgPool.query(`ALTER TABLE qr_codes ADD COLUMN IF NOT EXISTS name VARCHAR(255);`).catch(console.error);
|
|
pgPool.query(`ALTER TABLE qr_codes ADD COLUMN IF NOT EXISTS folder_id UUID REFERENCES folders(id) ON DELETE SET NULL;`).catch(console.error);
|
|
pgPool.query(`
|
|
CREATE TABLE IF NOT EXISTS qr_scans (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
qr_code_id UUID REFERENCES qr_codes(id) ON DELETE CASCADE,
|
|
ip_address VARCHAR(45),
|
|
user_agent TEXT,
|
|
os VARCHAR(50),
|
|
device_type VARCHAR(50),
|
|
browser VARCHAR(50),
|
|
country VARCHAR(100),
|
|
city VARCHAR(100),
|
|
scanned_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);`).catch(console.error);
|
|
|
|
pgPool.query(`
|
|
ALTER TABLE users
|
|
ADD COLUMN IF NOT EXISTS is_email_verified BOOLEAN DEFAULT false,
|
|
ADD COLUMN IF NOT EXISTS email_verification_token VARCHAR(255);
|
|
`).catch(console.error);
|
|
|
|
pgPool.query(`ALTER TABLE qr_codes ADD COLUMN IF NOT EXISTS tags TEXT[] DEFAULT '{}';`).catch(console.error);
|
|
pgPool.query(`ALTER TABLE qr_codes ADD COLUMN IF NOT EXISTS routing_data JSONB;`).catch(console.error);
|
|
pgPool.query(`ALTER TABLE qr_codes ADD COLUMN IF NOT EXISTS workspace_id UUID;`).catch(console.error);
|
|
pgPool.query(`ALTER TABLE folders ADD COLUMN IF NOT EXISTS workspace_id UUID;`).catch(console.error);
|
|
|
|
pgPool.query(`
|
|
CREATE TABLE IF NOT EXISTS workspace_members (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
owner_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
|
member_email VARCHAR(255) NOT NULL,
|
|
role VARCHAR(50) DEFAULT 'editor',
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(owner_id, member_email)
|
|
);
|
|
`).catch(console.error);
|
|
|
|
pgPool.query(`
|
|
CREATE TABLE IF NOT EXISTS app_settings (
|
|
key VARCHAR(100) PRIMARY KEY,
|
|
value TEXT,
|
|
category VARCHAR(50),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
`).then(() => {
|
|
return pgPool.query(`
|
|
INSERT INTO app_settings (key, value, category) VALUES
|
|
('icici_merchant_id', '', 'payments'),
|
|
('icici_api_key', '', 'payments'),
|
|
('google_safe_browsing_key', '', 'security'),
|
|
('google_client_id', '', 'auth'),
|
|
('apple_service_id', '', 'auth'),
|
|
('smtp_host', 'smtp.office365.com', 'smtp'),
|
|
('smtp_port', '587', 'smtp'),
|
|
('smtp_user', 'noreply@cavininfotech.com', 'smtp'),
|
|
('smtp_pass', 'xmkshjlszjbkcyzb', 'smtp'),
|
|
('smtp_secure', 'false', 'smtp')
|
|
ON CONFLICT (key) DO NOTHING;
|
|
`);
|
|
}).catch(console.error);
|
|
|
|
app.listen(port, () => {
|
|
console.log(`CK-QR API listening on port ${port}`);
|
|
});
|