const express = require('express')
const { Pool } = require('pg')
const cors = require('cors')
const app = express()
app.use(express.json())
app.use(cors())
// PostgreSQL connection
const pool = new Pool({
connectionString: process.env.DATABASE_URL
})
// POST /glue/identify
app.post('/glue/identify', async (req, res) => {
const { glue_user_id, email, traits, context } = req.body
if (!glue_user_id || !email) {
return res.status(400).json({ error: 'Missing required fields' })
}
try {
// Check for existing user with this email
const existing = await pool.query(
'SELECT * FROM users WHERE email = $1 AND glue_user_id != $2',
[email, glue_user_id]
)
if (existing.rows.length > 0) {
// Cross-device merge
const primaryUser = existing.rows[0]
// Add to linked_ids
await pool.query(
'UPDATE users SET linked_ids = array_append(linked_ids, $1) WHERE glue_user_id = $2',
[glue_user_id, primaryUser.glue_user_id]
)
// Merge events
await pool.query(
'UPDATE events SET glue_user_id = $1 WHERE glue_user_id = $2',
[primaryUser.glue_user_id, glue_user_id]
)
} else {
// Upsert user
await pool.query(`
INSERT INTO users (
glue_user_id, email, traits, identified, identified_at,
last_seen, last_touch_source, last_touch_medium
) VALUES ($1, $2, $3, true, NOW(), NOW(), $4, $5)
ON CONFLICT (glue_user_id) DO UPDATE SET
email = $2,
traits = $3,
identified = true,
identified_at = COALESCE(users.identified_at, NOW()),
last_seen = NOW(),
last_touch_source = $4,
last_touch_medium = $5
`, [
glue_user_id,
email,
JSON.stringify(traits),
context?.utm_source,
context?.utm_medium
])
}
res.json({ success: true, glue_user_id })
} catch (error) {
console.error('Identify error:', error)
res.status(500).json({ error: error.message })
}
})
// POST /glue/track
app.post('/glue/track', async (req, res) => {
const { glue_user_id, session_id, event, properties, timestamp } = req.body
try {
// Insert event
await pool.query(`
INSERT INTO events (event_id, glue_user_id, session_id, event, properties, timestamp)
VALUES ($1, $2, $3, $4, $5, $6)
`, [
`evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
glue_user_id,
session_id,
event,
JSON.stringify(properties),
new Date(timestamp)
])
// Update user
await pool.query(
'UPDATE users SET last_seen = NOW(), total_events = total_events + 1 WHERE glue_user_id = $1',
[glue_user_id]
)
res.json({ success: true })
} catch (error) {
console.error('Track error:', error)
res.status(500).json({ error: error.message })
}
})
// POST /glue/sync
app.post('/glue/sync', async (req, res) => {
const { glue_user_id, data } = req.body
try {
// Upsert user
await pool.query(`
INSERT INTO users (glue_user_id, email, traits, first_seen, last_seen)
VALUES ($1, $2, $3, $4, NOW())
ON CONFLICT (glue_user_id) DO UPDATE SET
email = COALESCE(users.email, $2),
traits = $3,
last_seen = NOW()
`, [
glue_user_id,
data.profile.email,
JSON.stringify(data.profile.traits),
data.profile.first_seen
])
// Bulk insert events
for (const event of data.events) {
await pool.query(`
INSERT INTO events (event_id, glue_user_id, session_id, event, properties, timestamp)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (event_id) DO NOTHING
`, [
event.event_id,
glue_user_id,
event.session_id,
event.event,
JSON.stringify(event.properties),
event.timestamp
])
}
res.json({ success: true, events_synced: data.events.length })
} catch (error) {
console.error('Sync error:', error)
res.status(500).json({ error: error.message })
}
})
const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
console.log(`DataGlue server running on port ${PORT}`)
})