Implementation, thanks amp

This commit is contained in:
2026-03-16 23:03:27 +13:00
parent b7539ac86e
commit ab508d827d
45 changed files with 2705 additions and 26 deletions

157
app/src/lib/poll-host.ts Normal file
View File

@@ -0,0 +1,157 @@
import { onMessage, sendToPeer, broadcast } from './peer.js';
import { getUserId } from './crypto.js';
import { getPollById, updatePollInStore, setRole, removeRole, setPollStatus } from './stores/polls.svelte.js';
import { canVote, canAddOption, canManageUsers, canManagePoll } from './permissions.js';
import type { Message, Poll, PollOption, Vote } from './types.js';
const processedCommands = new Set<string>();
let revision = 0;
export function startHosting(): () => void {
const unsub = onMessage(async (msg, peerId) => {
const userId = await getUserId();
switch (msg.type) {
case 'sync:request': {
const poll = getPollById(msg.payload.pollId);
if (poll && poll.ownerId === userId) {
sendToPeer(peerId, { type: 'poll:state', payload: poll });
}
break;
}
case 'poll:vote': {
if (processedCommands.has(msg.commandId)) {
sendToPeer(peerId, { type: 'ack', payload: { commandId: msg.commandId, revision } });
break;
}
const poll = getPollById(msg.payload.pollId);
if (!poll || poll.ownerId !== userId) break;
if (!canVote(poll, peerId)) {
sendToPeer(peerId, {
type: 'error',
payload: { commandId: msg.commandId, message: 'Not authorized to vote' }
});
break;
}
const vote: Vote = {
optionId: msg.payload.optionId,
voterId: poll.anonymous ? null : peerId,
timestamp: msg.payload.timestamp
};
const filteredVotes = poll.anonymous
? poll.votes
: poll.votes.filter((v) => v.voterId !== peerId);
const updated: Poll = { ...poll, votes: [...filteredVotes, vote] };
await updatePollInStore(updated);
revision++;
processedCommands.add(msg.commandId);
sendToPeer(peerId, { type: 'ack', payload: { commandId: msg.commandId, revision } });
broadcast({ type: 'poll:state', payload: updated }, peerId);
break;
}
case 'poll:option:add': {
if (processedCommands.has(msg.commandId)) {
sendToPeer(peerId, { type: 'ack', payload: { commandId: msg.commandId, revision } });
break;
}
const poll2 = getPollById(msg.payload.pollId);
if (!poll2 || poll2.ownerId !== userId) break;
if (!canAddOption(poll2, peerId)) {
sendToPeer(peerId, {
type: 'error',
payload: { commandId: msg.commandId, message: 'Not authorized to add options' }
});
break;
}
const option: PollOption = {
id: msg.payload.id,
text: msg.payload.text,
addedBy: peerId,
addedAt: msg.payload.addedAt
};
const updated2: Poll = { ...poll2, options: [...poll2.options, option] };
await updatePollInStore(updated2);
revision++;
processedCommands.add(msg.commandId);
sendToPeer(peerId, { type: 'ack', payload: { commandId: msg.commandId, revision } });
broadcast({ type: 'poll:state', payload: updated2 }, peerId);
break;
}
case 'poll:role:update': {
if (processedCommands.has(msg.commandId)) {
sendToPeer(peerId, { type: 'ack', payload: { commandId: msg.commandId, revision } });
break;
}
const poll3 = getPollById(msg.payload.pollId);
if (!poll3 || poll3.ownerId !== userId) break;
if (!canManageUsers(poll3, peerId)) {
sendToPeer(peerId, {
type: 'error',
payload: { commandId: msg.commandId, message: 'Not authorized to manage users' }
});
break;
}
await setRole(msg.payload.pollId, msg.payload.userId, msg.payload.role);
revision++;
processedCommands.add(msg.commandId);
const refreshed = getPollById(msg.payload.pollId);
sendToPeer(peerId, { type: 'ack', payload: { commandId: msg.commandId, revision } });
if (refreshed) broadcast({ type: 'poll:state', payload: refreshed }, peerId);
break;
}
case 'poll:status:update': {
if (processedCommands.has(msg.commandId)) {
sendToPeer(peerId, { type: 'ack', payload: { commandId: msg.commandId, revision } });
break;
}
const poll4 = getPollById(msg.payload.pollId);
if (!poll4 || poll4.ownerId !== userId) break;
if (!canManagePoll(poll4, peerId)) {
sendToPeer(peerId, {
type: 'error',
payload: { commandId: msg.commandId, message: 'Not authorized to manage poll' }
});
break;
}
await setPollStatus(msg.payload.pollId, msg.payload.status);
revision++;
processedCommands.add(msg.commandId);
const refreshed2 = getPollById(msg.payload.pollId);
sendToPeer(peerId, { type: 'ack', payload: { commandId: msg.commandId, revision } });
if (refreshed2) broadcast({ type: 'poll:state', payload: refreshed2 }, peerId);
break;
}
case 'user:profile': {
// Store peer's profile for display purposes
// Could be extended to a peer profile cache
break;
}
}
});
return unsub;
}