feat: initialize Discord clone application with core backend services and Electron frontend.
This commit is contained in:
@@ -147,7 +147,7 @@ router.post('/login/verify', async (req, res) => {
|
||||
|
||||
router.get('/users/public-keys', async (req, res) => {
|
||||
try {
|
||||
const result = await db.query('SELECT id, public_identity_key FROM users');
|
||||
const result = await db.query('SELECT id, username, public_identity_key FROM users');
|
||||
res.json(result.rows);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
@@ -4,7 +4,7 @@ const db = require('../db');
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const result = await db.query('SELECT * FROM channels ORDER BY name ASC');
|
||||
const result = await db.query("SELECT * FROM channels WHERE type != 'dm' ORDER BY name ASC");
|
||||
res.json(result.rows);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
79
Backend/routes/dms.js
Normal file
79
Backend/routes/dms.js
Normal file
@@ -0,0 +1,79 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('../db');
|
||||
|
||||
// POST /api/dms/open — Find-or-create a DM channel between two users
|
||||
router.post('/open', async (req, res) => {
|
||||
const { userId, targetUserId } = req.body;
|
||||
|
||||
if (!userId || !targetUserId) {
|
||||
return res.status(400).json({ error: 'userId and targetUserId required' });
|
||||
}
|
||||
|
||||
if (userId === targetUserId) {
|
||||
return res.status(400).json({ error: 'Cannot DM yourself' });
|
||||
}
|
||||
|
||||
// Deterministic channel name so the same pair always maps to one channel
|
||||
const sorted = [userId, targetUserId].sort();
|
||||
const dmName = `dm-${sorted[0]}-${sorted[1]}`;
|
||||
|
||||
try {
|
||||
// Check if DM channel already exists
|
||||
const existing = await db.query(
|
||||
'SELECT id FROM channels WHERE name = $1 AND type = $2',
|
||||
[dmName, 'dm']
|
||||
);
|
||||
|
||||
if (existing.rows.length > 0) {
|
||||
return res.json({ channelId: existing.rows[0].id, created: false });
|
||||
}
|
||||
|
||||
// Create the DM channel + participants in a transaction
|
||||
await db.query('BEGIN');
|
||||
|
||||
const channelResult = await db.query(
|
||||
'INSERT INTO channels (name, type) VALUES ($1, $2) RETURNING id',
|
||||
[dmName, 'dm']
|
||||
);
|
||||
const channelId = channelResult.rows[0].id;
|
||||
|
||||
await db.query(
|
||||
'INSERT INTO dm_participants (channel_id, user_id) VALUES ($1, $2), ($1, $3)',
|
||||
[channelId, userId, targetUserId]
|
||||
);
|
||||
|
||||
await db.query('COMMIT');
|
||||
|
||||
res.json({ channelId, created: true });
|
||||
} catch (err) {
|
||||
await db.query('ROLLBACK');
|
||||
console.error('Error opening DM:', err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/dms/user/:userId — List all DM channels for a user with the other user's info
|
||||
router.get('/user/:userId', async (req, res) => {
|
||||
const { userId } = req.params;
|
||||
|
||||
try {
|
||||
const result = await db.query(`
|
||||
SELECT c.id AS channel_id, c.name AS channel_name, c.created_at,
|
||||
other_user.id AS other_user_id, other_user.username AS other_username
|
||||
FROM dm_participants my
|
||||
JOIN channels c ON c.id = my.channel_id AND c.type = 'dm'
|
||||
JOIN dm_participants other ON other.channel_id = my.channel_id AND other.user_id != $1
|
||||
JOIN users other_user ON other_user.id = other.user_id
|
||||
WHERE my.user_id = $1
|
||||
ORDER BY c.created_at DESC
|
||||
`, [userId]);
|
||||
|
||||
res.json(result.rows);
|
||||
} catch (err) {
|
||||
console.error('Error fetching DM channels:', err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -16,7 +16,7 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
CREATE TABLE IF NOT EXISTS channels (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
type TEXT DEFAULT 'text' CHECK (type IN ('text', 'voice')),
|
||||
type TEXT DEFAULT 'text' CHECK (type IN ('text', 'voice', 'dm')),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
@@ -65,3 +65,9 @@ CREATE TABLE IF NOT EXISTS invites (
|
||||
key_version INTEGER NOT NULL, -- Which key version is inside? (So we can invalidate leaks)
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dm_participants (
|
||||
channel_id UUID REFERENCES channels(id) ON DELETE CASCADE,
|
||||
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (channel_id, user_id)
|
||||
);
|
||||
|
||||
32
Backend/scripts/migrate_dms.js
Normal file
32
Backend/scripts/migrate_dms.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const path = require('path');
|
||||
require('dotenv').config({ path: path.join(__dirname, '../.env') });
|
||||
const db = require('../db');
|
||||
|
||||
async function migrate() {
|
||||
try {
|
||||
console.log('Running DM migration...');
|
||||
|
||||
// 1. Update the check constraint to allow 'dm' type
|
||||
await db.query("ALTER TABLE channels DROP CONSTRAINT IF EXISTS channels_type_check");
|
||||
await db.query("ALTER TABLE channels ADD CONSTRAINT channels_type_check CHECK (type IN ('text', 'voice', 'dm'))");
|
||||
console.log(' Updated channels type constraint to allow dm.');
|
||||
|
||||
// 2. Create dm_participants table
|
||||
await db.query(`
|
||||
CREATE TABLE IF NOT EXISTS dm_participants (
|
||||
channel_id UUID REFERENCES channels(id) ON DELETE CASCADE,
|
||||
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (channel_id, user_id)
|
||||
)
|
||||
`);
|
||||
console.log(' Created dm_participants table.');
|
||||
|
||||
console.log('DM migration complete.');
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
console.error('DM migration failed:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
migrate();
|
||||
@@ -19,6 +19,7 @@ const channelRoutes = require('./routes/channels');
|
||||
const uploadRoutes = require('./routes/upload');
|
||||
const inviteRoutes = require('./routes/invites');
|
||||
const rolesRoutes = require('./routes/roles');
|
||||
const dmRoutes = require('./routes/dms');
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
@@ -39,6 +40,7 @@ app.use('/api/upload', uploadRoutes);
|
||||
app.use('/api/invites', inviteRoutes);
|
||||
app.use('/api/invites', inviteRoutes);
|
||||
app.use('/api/roles', rolesRoutes);
|
||||
app.use('/api/dms', dmRoutes);
|
||||
app.use('/api/voice', require('./routes/voice'));
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user