v3
This commit is contained in:
parent
4883125cda
commit
ef520f4259
31
api_v1_channels.php
Normal file
31
api_v1_channels.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
require_once 'auth/session.php';
|
||||
requireLogin();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$server_id = $_POST['server_id'] ?? 0;
|
||||
$name = $_POST['name'] ?? '';
|
||||
$type = $_POST['type'] ?? 'text';
|
||||
$user_id = $_SESSION['user_id'];
|
||||
|
||||
// Check if user is owner of the server or has permissions (simplified check for now: user must be a member)
|
||||
$stmt = db()->prepare("SELECT 1 FROM server_members WHERE server_id = ? AND user_id = ?");
|
||||
$stmt->execute([$server_id, $user_id]);
|
||||
|
||||
if ($stmt->fetch() && $name) {
|
||||
try {
|
||||
// Basic sanitization for channel name
|
||||
$name = strtolower(preg_replace('/[^a-zA-Z0-3\-]/', '-', $name));
|
||||
|
||||
$stmt = db()->prepare("INSERT INTO channels (server_id, name, type) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$server_id, $name, $type]);
|
||||
$channel_id = db()->lastInsertId();
|
||||
|
||||
header('Location: index.php?server_id=' . $server_id . '&channel_id=' . $channel_id);
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
die("Error creating channel: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
header('Location: index.php');
|
||||
55
api_v1_servers.php
Normal file
55
api_v1_servers.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
require_once 'auth/session.php';
|
||||
requireLogin();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = $_POST['action'] ?? 'create';
|
||||
$user_id = $_SESSION['user_id'];
|
||||
|
||||
if ($action === 'join') {
|
||||
$invite_code = $_POST['invite_code'] ?? '';
|
||||
$stmt = db()->prepare("SELECT id FROM servers WHERE invite_code = ?");
|
||||
$stmt->execute([$invite_code]);
|
||||
$server = $stmt->fetch();
|
||||
|
||||
if ($server) {
|
||||
$stmt = db()->prepare("INSERT IGNORE INTO server_members (server_id, user_id) VALUES (?, ?)");
|
||||
$stmt->execute([$server['id'], $user_id]);
|
||||
header('Location: index.php?server_id=' . $server['id']);
|
||||
exit;
|
||||
} else {
|
||||
die("Invalid invite code.");
|
||||
}
|
||||
}
|
||||
|
||||
$name = $_POST['name'] ?? '';
|
||||
|
||||
if ($name) {
|
||||
try {
|
||||
$db = db();
|
||||
$db->beginTransaction();
|
||||
|
||||
// Create server
|
||||
$invite_code = substr(strtoupper(md5(uniqid())), 0, 8);
|
||||
$stmt = $db->prepare("INSERT INTO servers (name, owner_id, invite_code) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$name, $user_id, $invite_code]);
|
||||
$server_id = $db->lastInsertId();
|
||||
|
||||
// Add owner as member
|
||||
$stmt = $db->prepare("INSERT INTO server_members (server_id, user_id) VALUES (?, ?)");
|
||||
$stmt->execute([$server_id, $user_id]);
|
||||
|
||||
// Create default channel
|
||||
$stmt = $db->prepare("INSERT INTO channels (server_id, name, type) VALUES (?, 'general', 'text')");
|
||||
$stmt->execute([$server_id]);
|
||||
|
||||
$db->commit();
|
||||
header('Location: index.php?server_id=' . $server_id);
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
$db->rollBack();
|
||||
die("Error creating server: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
header('Location: index.php');
|
||||
@ -120,6 +120,15 @@ body {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.server-icon.add-btn {
|
||||
color: #23a559;
|
||||
}
|
||||
|
||||
.server-icon.add-btn:hover {
|
||||
background-color: #23a559;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.channel-category {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.75em;
|
||||
@ -127,6 +136,46 @@ body {
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
padding-left: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.add-channel-btn {
|
||||
cursor: pointer;
|
||||
font-size: 1.2em;
|
||||
margin-right: 8px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.add-channel-btn:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Modals */
|
||||
.modal-content {
|
||||
background-color: var(--bg-channels);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
border-bottom: 1px solid var(--bg-servers);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
border-top: 1px solid var(--bg-servers);
|
||||
}
|
||||
|
||||
.form-control {
|
||||
background-color: var(--bg-servers);
|
||||
border: none;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
background-color: var(--bg-servers);
|
||||
color: var(--text-primary);
|
||||
box-shadow: 0 0 0 0.25rem rgba(88, 101, 242, 0.25);
|
||||
}
|
||||
|
||||
/* User Panel */
|
||||
|
||||
@ -43,6 +43,32 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
content: content,
|
||||
channel_id: channel_id
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
// If WS is connected, we might want to let WS handle the UI update
|
||||
// But for simplicity, we append here and also send to WS
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'message',
|
||||
data: JSON.stringify({
|
||||
...result.message,
|
||||
channel_id: channel_id
|
||||
})
|
||||
}));
|
||||
} else {
|
||||
appendMessage(result.message);
|
||||
messagesList.scrollTop = messagesList.scrollHeight;
|
||||
}
|
||||
} else {
|
||||
alert('Error: ' + result.error);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to send message:', err);
|
||||
}
|
||||
});
|
||||
|
||||
// Voice
|
||||
const voiceHandler = new VoiceChannel(ws);
|
||||
document.querySelectorAll('.voice-item').forEach(item => {
|
||||
@ -57,6 +83,28 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
});
|
||||
|
||||
function appendMessage(msg) {
|
||||
const messagesList = document.getElementById('messages-list');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'message-item';
|
||||
div.innerHTML = `
|
||||
<div class="message-avatar"></div>
|
||||
<div class="message-content">
|
||||
<div class="message-author">
|
||||
${msg.username}
|
||||
<span class="message-time">${msg.time}</span>
|
||||
</div>
|
||||
<div class="message-text">
|
||||
${msg.content.replace(/\n/g, '<br>')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
messagesList.appendChild(div);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
|
||||
@ -12,7 +12,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
try {
|
||||
$stmt = db()->prepare("INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$username, $email, $hash]);
|
||||
$_SESSION['user_id'] = db()->lastInsertId();
|
||||
$userId = db()->lastInsertId();
|
||||
|
||||
// Add to default server
|
||||
$stmt = db()->prepare("INSERT IGNORE INTO server_members (server_id, user_id) VALUES (1, ?)");
|
||||
$stmt->execute([$userId]);
|
||||
|
||||
$_SESSION['user_id'] = $userId;
|
||||
header('Location: ../index.php');
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
|
||||
@ -67,6 +67,15 @@ CREATE TABLE IF NOT EXISTS user_roles (
|
||||
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS server_members (
|
||||
server_id INT NOT NULL,
|
||||
user_id INT NOT NULL,
|
||||
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (server_id, user_id),
|
||||
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Seed initial data
|
||||
INSERT IGNORE INTO users (id, username, email, password_hash, status) VALUES
|
||||
(1, 'System', 'system@local', '$2y$10$xyz', 'online');
|
||||
@ -75,6 +84,8 @@ INSERT IGNORE INTO servers (id, name, owner_id, invite_code) VALUES
|
||||
(1, 'General Community', 1, 'GEN-123'),
|
||||
(2, 'Flatlogic Devs', 1, 'DEV-456');
|
||||
|
||||
INSERT IGNORE INTO server_members (server_id, user_id) VALUES (1, 1), (2, 1);
|
||||
|
||||
INSERT IGNORE INTO channels (id, server_id, name, type) VALUES
|
||||
(1, 1, 'general', 'text'),
|
||||
(2, 1, 'random', 'text'),
|
||||
|
||||
146
index.php
146
index.php
@ -5,10 +5,20 @@ requireLogin();
|
||||
$user = getCurrentUser();
|
||||
$current_user_id = $user['id'];
|
||||
|
||||
// Fetch servers
|
||||
$servers = db()->query("SELECT * FROM servers LIMIT 10")->fetchAll();
|
||||
// Fetch servers user is member of
|
||||
$stmt = db()->prepare("
|
||||
SELECT s.* FROM servers s
|
||||
JOIN server_members sm ON s.id = sm.server_id
|
||||
WHERE sm.user_id = ?
|
||||
LIMIT 20
|
||||
");
|
||||
$stmt->execute([$current_user_id]);
|
||||
$servers = $stmt->fetchAll();
|
||||
$active_server_id = $_GET['server_id'] ?? ($servers[0]['id'] ?? 1);
|
||||
|
||||
// If no servers found, we might want to show a default or an empty state
|
||||
// For now, let's assume the seed data or the first server works
|
||||
|
||||
// Fetch channels
|
||||
$stmt = db()->prepare("SELECT * FROM channels WHERE server_id = ?");
|
||||
$stmt->execute([$active_server_id]);
|
||||
@ -30,6 +40,16 @@ $messages = $stmt->fetchAll();
|
||||
$current_channel_name = 'general';
|
||||
foreach($channels as $c) if($c['id'] == $active_channel_id) $current_channel_name = $c['name'];
|
||||
|
||||
// Fetch members
|
||||
$stmt = db()->prepare("
|
||||
SELECT u.username, u.avatar_url, u.status
|
||||
FROM users u
|
||||
JOIN server_members sm ON u.id = sm.user_id
|
||||
WHERE sm.server_id = ?
|
||||
");
|
||||
$stmt->execute([$active_server_id]);
|
||||
$members = $stmt->fetchAll();
|
||||
|
||||
// SEO & Env tags
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Discord-like messaging app built with PHP';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
@ -69,15 +89,25 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<?php echo mb_substr($s['name'], 0, 1); ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
<a href="#" class="server-icon add-btn" title="Add a Server" data-bs-toggle="modal" data-bs-target="#addServerModal">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Channels Sidebar -->
|
||||
<div class="channels-sidebar">
|
||||
<div class="channels-header">
|
||||
<?php echo htmlspecialchars($servers[0]['name'] ?? 'Server'); ?>
|
||||
<?php
|
||||
$active_server_name = 'Server';
|
||||
foreach($servers as $s) if($s['id'] == $active_server_id) $active_server_name = $s['name'];
|
||||
echo htmlspecialchars($active_server_name);
|
||||
?>
|
||||
</div>
|
||||
<div class="channels-list">
|
||||
<div class="channel-category">Text Channels</div>
|
||||
<div class="channel-category">
|
||||
<span>Text Channels</span>
|
||||
<span class="add-channel-btn" title="Create Channel" data-bs-toggle="modal" data-bs-target="#addChannelModal" data-type="text">+</span>
|
||||
</div>
|
||||
<?php foreach($channels as $c): if($c['type'] !== 'text') continue; ?>
|
||||
<a href="?server_id=<?php echo $active_server_id; ?>&channel_id=<?php echo $c['id']; ?>"
|
||||
class="channel-item <?php echo $c['id'] == $active_channel_id ? 'active' : ''; ?>">
|
||||
@ -85,7 +115,10 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div class="channel-category" style="margin-top: 16px;">Voice Channels</div>
|
||||
<div class="channel-category" style="margin-top: 16px;">
|
||||
<span>Voice Channels</span>
|
||||
<span class="add-channel-btn" title="Create Channel" data-bs-toggle="modal" data-bs-target="#addChannelModal" data-type="voice">+</span>
|
||||
</div>
|
||||
<?php foreach($channels as $c): if($c['type'] !== 'voice') continue; ?>
|
||||
<div class="channel-item voice-item" data-channel-id="<?php echo $c['id']; ?>">
|
||||
<?php echo htmlspecialchars($c['name']); ?>
|
||||
@ -150,17 +183,110 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
|
||||
<!-- Members Sidebar -->
|
||||
<div class="members-sidebar">
|
||||
<div style="color: var(--text-muted); font-size: 0.75em; text-transform: uppercase; font-weight: bold; margin-bottom: 8px;">
|
||||
Online — 1
|
||||
<div style="color: var(--text-muted); font-size: 0.75em; text-transform: uppercase; font-weight: bold; margin-bottom: 16px;">
|
||||
Members — <?php echo count($members); ?>
|
||||
</div>
|
||||
<div class="channel-item" style="color: var(--text-primary);">
|
||||
<div class="message-avatar" style="width: 32px; height: 32px; background-color: #23a559;"></div>
|
||||
System
|
||||
<?php foreach($members as $m): ?>
|
||||
<div class="channel-item" style="color: var(--text-primary); margin-bottom: 8px;">
|
||||
<div class="message-avatar" style="width: 32px; height: 32px; background-color: <?php echo $m['status'] == 'online' ? '#23a559' : '#80848e'; ?>; position: relative;">
|
||||
<?php if($m['status'] == 'online'): ?>
|
||||
<div style="position: absolute; bottom: 0; right: 0; width: 10px; height: 10px; background-color: #23a559; border-radius: 50%; border: 2px solid var(--bg-members);"></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
<?php echo htmlspecialchars($m['username']); ?>
|
||||
</span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Server Modal -->
|
||||
<div class="modal fade" id="addServerModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Create or Join a server</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body p-0">
|
||||
<ul class="nav nav-tabs nav-fill" id="serverTabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<button class="nav-link active text-white border-0 bg-transparent" id="create-tab" data-bs-toggle="tab" data-bs-target="#create-pane" type="button">Create</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button class="nav-link text-white border-0 bg-transparent" id="join-tab" data-bs-toggle="tab" data-bs-target="#join-pane" type="button">Join</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content p-3">
|
||||
<div class="tab-pane fade show active" id="create-pane">
|
||||
<form action="api_v1_servers.php" method="POST">
|
||||
<p style="color: var(--text-muted); font-size: 0.9em;">Give your new server a personality with a name.</p>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Server Name</label>
|
||||
<input type="text" name="name" class="form-control" placeholder="My Awesome Server" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100" style="background-color: var(--blurple); border: none;">Create Server</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="join-pane">
|
||||
<form action="api_v1_servers.php" method="POST">
|
||||
<input type="hidden" name="action" value="join">
|
||||
<p style="color: var(--text-muted); font-size: 0.9em;">Enter an invite code to join an existing server.</p>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Invite Code</label>
|
||||
<input type="text" name="invite_code" class="form-control" placeholder="GEN-123" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success w-100" style="background-color: #23a559; border: none;">Join Server</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Channel Modal -->
|
||||
<div class="modal fade" id="addChannelModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Create Channel</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form action="api_v1_channels.php" method="POST">
|
||||
<input type="hidden" name="server_id" value="<?php echo $active_server_id; ?>">
|
||||
<input type="hidden" name="type" id="channel-type-input" value="text">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Channel Name</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-dark border-0 text-muted" id="channel-hash-prefix">#</span>
|
||||
<input type="text" name="name" class="form-control" placeholder="new-channel" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-link text-white text-decoration-none" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" style="background-color: var(--blurple); border: none; padding: 10px 24px;">Create Channel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="assets/js/voice.js?v=<?php echo time(); ?>"></script>
|
||||
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||||
<script>
|
||||
// Handle channel type in modal
|
||||
document.querySelectorAll('.add-channel-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const type = this.getAttribute('data-type');
|
||||
document.getElementById('channel-type-input').value = type;
|
||||
document.getElementById('channel-hash-prefix').textContent = type === 'text' ? '#' : '🔊';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user