-
Notifications
You must be signed in to change notification settings - Fork 20
Peer Connections & Secure Messaging #73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Ananya44444
wants to merge
3
commits into
alphaonelabs:main
Choose a base branch
from
Ananya44444:msg
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| -- Migration 0009: Peer connections, peer messages, and secure messages | ||
|
|
||
| CREATE TABLE IF NOT EXISTS peer_connections ( | ||
| id TEXT PRIMARY KEY, | ||
| sender_id TEXT NOT NULL, | ||
| receiver_id TEXT NOT NULL, | ||
| status TEXT NOT NULL DEFAULT 'pending', | ||
| created_at TEXT NOT NULL DEFAULT (datetime('now')), | ||
| updated_at TEXT NOT NULL DEFAULT (datetime('now')), | ||
| CHECK (sender_id <> receiver_id), | ||
| UNIQUE (sender_id, receiver_id), | ||
| FOREIGN KEY (sender_id) REFERENCES users(id) ON DELETE CASCADE, | ||
| FOREIGN KEY (receiver_id) REFERENCES users(id) ON DELETE CASCADE | ||
| ); | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_pc_sender ON peer_connections(sender_id); | ||
| CREATE INDEX IF NOT EXISTS idx_pc_receiver ON peer_connections(receiver_id); | ||
| CREATE INDEX IF NOT EXISTS idx_pc_status ON peer_connections(status); | ||
|
|
||
| CREATE UNIQUE INDEX IF NOT EXISTS idx_pc_pair_unique | ||
| ON peer_connections ( | ||
| CASE WHEN sender_id < receiver_id THEN sender_id ELSE receiver_id END, | ||
| CASE WHEN sender_id < receiver_id THEN receiver_id ELSE sender_id END | ||
| ); | ||
|
|
||
| CREATE TABLE IF NOT EXISTS peer_messages ( | ||
| id TEXT PRIMARY KEY, | ||
| sender_id TEXT NOT NULL, | ||
| receiver_id TEXT NOT NULL, | ||
| content_enc TEXT NOT NULL, | ||
| is_read INTEGER NOT NULL DEFAULT 0, | ||
| read_at TEXT, | ||
| created_at TEXT NOT NULL DEFAULT (datetime('now')), | ||
| FOREIGN KEY (sender_id) REFERENCES users(id) ON DELETE CASCADE, | ||
| FOREIGN KEY (receiver_id) REFERENCES users(id) ON DELETE CASCADE | ||
| ); | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_pm_sender ON peer_messages(sender_id); | ||
| CREATE INDEX IF NOT EXISTS idx_pm_receiver ON peer_messages(receiver_id); | ||
| CREATE INDEX IF NOT EXISTS idx_pm_thread ON peer_messages(sender_id, receiver_id); | ||
|
|
||
| CREATE TABLE IF NOT EXISTS secure_messages ( | ||
| id TEXT PRIMARY KEY, | ||
| sender_id TEXT NOT NULL, | ||
| receiver_id TEXT NOT NULL, | ||
| content_enc TEXT NOT NULL, | ||
| is_starred INTEGER NOT NULL DEFAULT 0, | ||
| is_read INTEGER NOT NULL DEFAULT 0, | ||
| read_at TEXT, | ||
| created_at TEXT NOT NULL DEFAULT (datetime('now')), | ||
| FOREIGN KEY (sender_id) REFERENCES users(id) ON DELETE CASCADE, | ||
| FOREIGN KEY (receiver_id) REFERENCES users(id) ON DELETE CASCADE | ||
| ); | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_sm_receiver ON secure_messages(receiver_id); | ||
| CREATE INDEX IF NOT EXISTS idx_sm_sender ON secure_messages(sender_id); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| <!DOCTYPE html> | ||
| <html lang="en" class="scroll-smooth"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <meta name="description" content="Peer Messages - Alpha One Labs"> | ||
| <title>Peer Messages - Alpha One Labs</title> | ||
| <link rel="icon" type="image/png" href="https://alphaonelabs.com/static/images/logo.png" /> | ||
| <script src="https://cdn.tailwindcss.com"></script> | ||
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> | ||
| <script> | ||
| tailwind.config = { darkMode: 'class', theme: { extend: {} } }; | ||
| (function () { | ||
| if (localStorage.getItem('darkMode') === 'true') document.documentElement.classList.add('dark'); | ||
| })(); | ||
| </script> | ||
| </head> | ||
| <body class="min-h-screen flex flex-col bg-white text-gray-900 dark:bg-black dark:text-gray-100 transition-colors duration-300 overflow-x-hidden"> | ||
|
|
||
| <div id="site-navbar"></div> | ||
|
|
||
| <section class="border-b border-gray-200 dark:border-gray-800 bg-white/70 dark:bg-black/40 backdrop-blur"> | ||
| <div class="max-w-3xl mx-auto px-4 py-5 flex items-center justify-between gap-4"> | ||
| <div> | ||
| <a href="/peers.html" class="text-sm text-teal-600 dark:text-teal-400 hover:underline mb-1 inline-block"> | ||
| <i class="fas fa-arrow-left mr-1"></i> Back to Peers | ||
| </a> | ||
| <h1 id="peer-title" class="text-2xl font-bold flex items-center gap-2"> | ||
| <i class="fas fa-comments text-teal-600 dark:text-teal-400"></i> Messages | ||
| </h1> | ||
| </div> | ||
| </div> | ||
| </section> | ||
|
|
||
| <main class="flex-1 max-w-3xl w-full mx-auto px-4 py-6"> | ||
| <div class="rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden bg-white dark:bg-gray-900"> | ||
| <div id="messages-container" class="h-[480px] overflow-y-auto p-4 space-y-3"> | ||
| <div class="animate-pulse h-16 bg-gray-200 dark:bg-gray-800 rounded-lg"></div> | ||
| </div> | ||
| <form id="send-form" class="border-t border-gray-200 dark:border-gray-700 p-4 flex gap-2"> | ||
| <label for="message-input" class="sr-only">Message</label> | ||
| <input id="message-input" type="text" placeholder="Type a message…" required | ||
| class="flex-1 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-teal-500" /> | ||
| <button type="submit" | ||
| class="bg-teal-600 hover:bg-teal-700 text-white font-semibold px-5 py-2 rounded-lg text-sm"> | ||
| Send | ||
| </button> | ||
| </form> | ||
| </div> | ||
| </main> | ||
|
|
||
| <div id="site-footer"></div> | ||
|
|
||
| <script src="/js/layout.js" defer></script> | ||
| <script> | ||
| const API = ''; | ||
| const token = localStorage.getItem('edu_token'); | ||
| const user = JSON.parse(localStorage.getItem('edu_user') || 'null'); | ||
| const esc = window.esc || (s => String(s || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')); | ||
|
|
||
| const params = new URLSearchParams(window.location.search); | ||
| const peerId = params.get('peer_id'); | ||
|
|
||
| if (!token || !user) window.location.href = '/login.html'; | ||
| if (!peerId) window.location.href = '/peers.html'; | ||
|
|
||
| let pollTimer = null; | ||
|
|
||
| async function apiFetch(path, opts = {}) { | ||
| const res = await fetch(`${API}${path}`, { | ||
| ...opts, | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| ...(opts.headers || {}), | ||
| Authorization: `Bearer ${token}`, | ||
| }, | ||
| }); | ||
| if (res.status === 401) { | ||
| localStorage.removeItem('edu_token'); | ||
| localStorage.removeItem('edu_user'); | ||
| window.location.href = '/login.html'; | ||
| return null; | ||
| } | ||
| return res; | ||
| } | ||
|
|
||
| function formatTime(iso) { | ||
| if (!iso) return ''; | ||
| const d = new Date(iso.includes('T') ? iso : iso.replace(' ', 'T') + 'Z'); | ||
| if (Number.isNaN(d.getTime())) return iso; | ||
| return d.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); | ||
| } | ||
|
|
||
| function renderMessages(messages) { | ||
| const box = document.getElementById('messages-container'); | ||
| if (!messages.length) { | ||
| box.innerHTML = '<p class="text-center text-gray-500 py-12">No messages yet. Start the conversation!</p>'; | ||
| return; | ||
| } | ||
| box.innerHTML = messages.map(m => ` | ||
| <div class="flex ${m.mine ? 'justify-end' : 'justify-start'}"> | ||
| <div class="max-w-[75%] rounded-lg px-4 py-2 ${m.mine | ||
| ? 'bg-teal-600 text-white' | ||
| : 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100'}"> | ||
| <p class="text-sm whitespace-pre-wrap break-words">${esc(m.content)}</p> | ||
| <p class="text-xs mt-1 opacity-70">${formatTime(m.created_at)}</p> | ||
| </div> | ||
| </div>`).join(''); | ||
| box.scrollTop = box.scrollHeight; | ||
| } | ||
|
|
||
| async function loadThread() { | ||
| const res = await apiFetch(`/api/peers/messages/${encodeURIComponent(peerId)}`); | ||
| if (!res) return; | ||
| if (res.status === 403) { | ||
| document.getElementById('messages-container').innerHTML = | ||
| '<p class="text-center text-red-600 py-12">You must be connected with this user to view messages.</p>'; | ||
| document.getElementById('send-form').classList.add('hidden'); | ||
| return; | ||
| } | ||
| if (!res.ok) throw new Error('Failed to load messages'); | ||
| const body = await res.json(); | ||
| const data = body.data || {}; | ||
| const peer = data.peer || {}; | ||
| document.getElementById('peer-title').innerHTML = | ||
| `<i class="fas fa-comments text-teal-600 dark:text-teal-400"></i> ${esc(peer.name || peer.username || 'Peer')}`; | ||
| renderMessages(data.messages || []); | ||
| } | ||
|
|
||
| document.getElementById('send-form').addEventListener('submit', async e => { | ||
| e.preventDefault(); | ||
| const input = document.getElementById('message-input'); | ||
| const submit = e.submitter || document.querySelector('#send-form button[type="submit"]'); | ||
| const content = input.value.trim(); | ||
| if (!content) return; | ||
| input.disabled = true; | ||
| if (submit) submit.disabled = true; | ||
| try { | ||
| const res = await apiFetch(`/api/peers/messages/${encodeURIComponent(peerId)}`, { | ||
| method: 'POST', | ||
| body: JSON.stringify({ content }), | ||
| }); | ||
| if (!res) return; | ||
| const body = await res.json(); | ||
| if (!res.ok) { | ||
| alert(body.error || 'Send failed'); | ||
| return; | ||
| } | ||
| input.value = ''; | ||
| await loadThread(); | ||
| } finally { | ||
| input.disabled = false; | ||
| if (submit) submit.disabled = false; | ||
| input.focus(); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| }); | ||
|
|
||
| loadThread().catch(e => { | ||
| document.getElementById('messages-container').innerHTML = | ||
| `<p class="text-red-600 text-center py-8">${esc(e.message)}</p>`; | ||
| }); | ||
|
|
||
| pollTimer = setInterval(() => loadThread().catch(() => {}), 15000); | ||
| window.addEventListener('beforeunload', () => clearInterval(pollTimer)); | ||
| </script> | ||
| </body> | ||
| </html> | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.