Implementation, thanks amp
This commit is contained in:
157
app/src/lib/poll-host.ts
Normal file
157
app/src/lib/poll-host.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user