FEATURE: added pols to frontend
This commit is contained in:
parent
c0391f8e69
commit
b5982063c4
71
index.html
71
index.html
|
|
@ -523,7 +523,6 @@
|
|||
let pollInterval = null;
|
||||
let selectedGifUrl = null;
|
||||
let votedPolls = new Set();
|
||||
let pollsData = [];
|
||||
|
||||
function loadVotedPolls() {
|
||||
try {
|
||||
|
|
@ -711,7 +710,7 @@
|
|||
if (data.success) {
|
||||
showToast('Poll created!', 'success');
|
||||
pollModal.classList.remove('open');
|
||||
loadPolls();
|
||||
loadMessages();
|
||||
} else {
|
||||
showToast(data.error || 'Failed to create poll', 'error');
|
||||
}
|
||||
|
|
@ -745,27 +744,34 @@
|
|||
renderChannels();
|
||||
messagesEl.innerHTML = '<div class="empty-state">Loading...</div>';
|
||||
loadMessages();
|
||||
loadPolls();
|
||||
}
|
||||
|
||||
async function loadPolls() {
|
||||
async function loadMessages() {
|
||||
try {
|
||||
const r = await fetch(`/api/polls?channel=${currentChannel}`);
|
||||
const r = await fetch(`/api/messages?channel=${currentChannel}&limit=50`);
|
||||
const data = await r.json();
|
||||
if (data.success) pollsData = data.polls || [];
|
||||
else pollsData = [];
|
||||
if (data.success) {
|
||||
window._lastMessages = data.messages || [];
|
||||
renderMessages(window._lastMessages);
|
||||
}
|
||||
else showToast('Failed to load messages', 'error');
|
||||
} catch (e) {
|
||||
pollsData = [];
|
||||
showToast('Network error', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function renderPolls() {
|
||||
if (!pollsData.length) return '';
|
||||
function renderMessages(msgs) {
|
||||
if (msgs.length === 0) {
|
||||
messagesEl.innerHTML = '<div class="empty-state">💬 No messages yet. Be the first!</div>';
|
||||
return;
|
||||
}
|
||||
let html = '';
|
||||
pollsData.forEach(poll => {
|
||||
msgs.forEach(msg => {
|
||||
if (msg.poll) {
|
||||
const poll = msg.poll;
|
||||
const isClosed = poll.status === 'closed';
|
||||
const isExpired = poll.expires_at && Date.now() > poll.expires_at;
|
||||
const hasVoted = votedPolls.has(poll.id) || poll.options.some(o => o.votes > 0 && false);
|
||||
const hasVoted = votedPolls.has(poll.id);
|
||||
const total = poll.total_votes || 0;
|
||||
let optionsHtml = '';
|
||||
poll.options.forEach(opt => {
|
||||
|
|
@ -785,8 +791,7 @@
|
|||
</div>`;
|
||||
}
|
||||
});
|
||||
const statusLabel = isClosed || poll.status === 'closed' ? 'Closed' :
|
||||
isExpired ? 'Expired' : 'Active';
|
||||
const statusLabel = isClosed ? 'Closed' : isExpired ? 'Expired' : 'Active';
|
||||
const statusClass = isClosed || isExpired ? 'closed' : 'active';
|
||||
const creatorLabel = poll.is_anonymous ? 'Anonymous' : escapeHtml(poll.creator_ip);
|
||||
html += `<div class="poll-card" data-poll-id="${poll.id}">
|
||||
|
|
@ -794,32 +799,7 @@
|
|||
<div class="poll-meta">by ${creatorLabel} · ${total} vote${total !== 1 ? 's' : ''} · <span class="poll-status ${statusClass}">${statusLabel}</span></div>
|
||||
<div class="poll-options">${optionsHtml}</div>
|
||||
</div>`;
|
||||
});
|
||||
return html;
|
||||
}
|
||||
|
||||
async function loadMessages() {
|
||||
try {
|
||||
const r = await fetch(`/api/messages?channel=${currentChannel}&limit=50`);
|
||||
const data = await r.json();
|
||||
if (data.success) {
|
||||
window._lastMessages = data.messages || [];
|
||||
renderMessages(window._lastMessages);
|
||||
}
|
||||
else showToast('Failed to load messages', 'error');
|
||||
} catch (e) {
|
||||
showToast('Network error', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function renderMessages(msgs) {
|
||||
const pollsHtml = renderPolls();
|
||||
if (msgs.length === 0 && !pollsHtml) {
|
||||
messagesEl.innerHTML = '<div class="empty-state">💬 No messages yet. Be the first!</div>';
|
||||
return;
|
||||
}
|
||||
let html = pollsHtml;
|
||||
msgs.forEach(msg => {
|
||||
} else {
|
||||
const time = new Date(msg.timestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
||||
let contentHtml = escapeHtml(msg.content || '');
|
||||
let gifToShow = msg.gif_url || null;
|
||||
|
|
@ -835,6 +815,7 @@
|
|||
<div class="msg-content">${contentHtml}</div>
|
||||
<div class="msg-time">${time}</div>
|
||||
</div>`;
|
||||
}
|
||||
});
|
||||
messagesEl.innerHTML = html;
|
||||
attachPollHandlers();
|
||||
|
|
@ -859,10 +840,10 @@
|
|||
votedPolls.add(pollId);
|
||||
saveVotedPolls();
|
||||
showToast('Vote recorded!', 'success');
|
||||
loadPolls().then(() => renderMessages(window._lastMessages || []));
|
||||
loadMessages();
|
||||
} else {
|
||||
showToast(data.error || 'Failed to vote', 'error');
|
||||
loadPolls().then(() => renderMessages(window._lastMessages || []));
|
||||
loadMessages();
|
||||
}
|
||||
} catch (e) {
|
||||
showToast('Network error', 'error');
|
||||
|
|
@ -904,17 +885,13 @@
|
|||
loadVotedPolls();
|
||||
renderChannels();
|
||||
loadMessages();
|
||||
loadPolls();
|
||||
|
||||
sendBtn.addEventListener('click', sendMessage);
|
||||
inputEl.addEventListener('keydown', e => { if (e.key === 'Enter') sendMessage(); });
|
||||
|
||||
// Auto-poll every 3 seconds
|
||||
// Auto-refresh every 3 seconds
|
||||
setInterval(() => {
|
||||
loadMessages();
|
||||
loadPolls().then(() => {
|
||||
if (window._lastMessages) renderMessages(window._lastMessages);
|
||||
});
|
||||
}, 3000);
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -22,6 +22,7 @@ class Messages(Base):
|
|||
ip: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
content: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
gif_url: Mapped[Optional[str]] = mapped_column(Text, nullable=True, default=None)
|
||||
poll_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True, default=None)
|
||||
timestamp: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
class Poll(Base):
|
||||
|
|
|
|||
29
models/db.py
29
models/db.py
|
|
@ -18,6 +18,16 @@ class DBfrontend:
|
|||
self.engine = make_engine(db_cfg)
|
||||
self.Session = sessionmaker(bind=self.engine)
|
||||
self.cipher = self._get_cipher(secret, salt)
|
||||
self._migrate()
|
||||
|
||||
def _migrate(self):
|
||||
with self.Session() as session:
|
||||
from sqlalchemy import inspect, text
|
||||
inspector = inspect(session.connection())
|
||||
columns = [c["name"] for c in inspector.get_columns("messages")]
|
||||
if "poll_id" not in columns:
|
||||
session.execute(text("ALTER TABLE messages ADD COLUMN poll_id INTEGER"))
|
||||
session.commit()
|
||||
|
||||
@staticmethod
|
||||
def _get_cipher(secret: bytes, salt: bytes) -> Fernet:
|
||||
|
|
@ -34,13 +44,14 @@ class DBfrontend:
|
|||
except Exception:
|
||||
return "[Decryption failed]"
|
||||
|
||||
def add_message(self, channel: str, ip: str, content: str, gif_url: Optional[str] = None) -> int:
|
||||
def add_message(self, channel: str, ip: str, content: str, gif_url: Optional[str] = None, poll_id: Optional[int] = None) -> int:
|
||||
with self.Session() as session:
|
||||
msg = Messages(
|
||||
channel=channel,
|
||||
ip=ip,
|
||||
content=self._encrypt(content),
|
||||
gif_url=gif_url,
|
||||
poll_id=poll_id,
|
||||
timestamp=int(time.time() * 1000),
|
||||
)
|
||||
session.add(msg)
|
||||
|
|
@ -57,6 +68,11 @@ class DBfrontend:
|
|||
)
|
||||
result = []
|
||||
for msg in reversed(q.all()):
|
||||
poll_data = None
|
||||
if msg.poll_id:
|
||||
poll = session.get(Poll, msg.poll_id)
|
||||
if poll and poll.status != "deleted":
|
||||
poll_data = self._serialize_poll(session, poll)
|
||||
result.append(
|
||||
{
|
||||
"id": msg.id,
|
||||
|
|
@ -64,6 +80,8 @@ class DBfrontend:
|
|||
"ip": msg.ip,
|
||||
"content": self._decrypt(msg.content),
|
||||
"gif_url": msg.gif_url,
|
||||
"poll_id": msg.poll_id,
|
||||
"poll": poll_data,
|
||||
"timestamp": msg.timestamp,
|
||||
}
|
||||
)
|
||||
|
|
@ -106,6 +124,15 @@ class DBfrontend:
|
|||
opt = PollOption(poll_id=poll.id, label=self._encrypt(label), position=i)
|
||||
session.add(opt)
|
||||
|
||||
msg = Messages(
|
||||
channel=channel,
|
||||
ip=creator_ip,
|
||||
content=self._encrypt(question),
|
||||
poll_id=poll.id,
|
||||
timestamp=int(time.time() * 1000),
|
||||
)
|
||||
session.add(msg)
|
||||
|
||||
session.commit()
|
||||
return poll.id
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue