feat: initialize Discord clone application with core backend services and Electron frontend.

This commit is contained in:
Bryan1029384756
2026-02-09 23:54:49 -06:00
parent e64cf20116
commit 516cfdbbd8
26 changed files with 622 additions and 161 deletions

View File

@@ -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);

View File

@@ -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
View 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;

View File

@@ -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)
);

View 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();

View File

@@ -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) => {