[READ-ONLY] Mirror of https://github.com/danielroe/roe.dev. This is the code and content for my personal website, built in Nuxt. roe.dev
0

Configure Feed

Select the types of activity you want to include in your feed.

roe.dev / partykit / server.ts
3.5 kB 104 lines
1import type { PartyKitServer, Room } from 'partykit/server' 2import { isValidEmoji } from '#shared/utils/emoji' 3 4export default { 5 async onConnect (ws, party) { 6 // 1. send current vote count if applicable 7 ws.send(`count:${await getCount(party)}`) 8 9 // 2. let everyone know someone new is viewing the site 10 party.broadcast(`connections:${[...party.getConnections()].length}`) 11 12 // 3. send current feedback level 13 if (party.id === 'feedback') { 14 ws.send(`feedback:${JSON.stringify(await party.storage.get('feedback') || [])}`) 15 } 16 17 // 4. let people know if I'm streaming 18 ws.send(`status:${(await party.storage.get('status')) || 'default'}`) 19 }, 20 async onRequest (request, party) { 21 if (request.method !== 'POST') 22 return new Response('Invalid request', { status: 422 }) 23 24 const body = await request.text().then(r => (r ? JSON.parse(r) : {})) 25 const { status, emoji, type = status ? 'status' : (emoji ? 'reaction' : 'vote') } = body as { 26 type?: 'vote' | 'status' | 'feedback' | 'reaction' 27 status?: string 28 emoji?: string 29 } 30 31 // 5. allow one-off live voting via link 32 if (type === 'vote') { 33 const val = (await getCount(party)) + 1 34 await party.storage.put('count', val) 35 party.broadcast(`count:${val}`) 36 return new Response(null, { status: 204 }) 37 } 38 39 // 6. allow one-off feedback submissions via link 40 if (type === 'feedback') { 41 await party.storage.transaction(async tx => { 42 const feedback = await tx.get<string[]>('feedback') || [] 43 feedback.push(status!) 44 await tx.put('feedback', feedback) 45 party.broadcast(`feedback:${JSON.stringify(feedback)}`) 46 }) 47 return new Response(null, { status: 204 }) 48 } 49 50 // 7. tell people if I'm going live 51 if (type === 'status') { 52 if (!status || !['live', 'default'].includes(status)) 53 return new Response('Invalid status', { status: 422 }) 54 55 if (request.headers.get('authorization') !== party.env.PARTYKIT_TOKEN) 56 return new Response('Unauthorised', { status: 401 }) 57 58 party.storage.put('status', status) 59 party.broadcast(`status:${status}`) 60 return new Response(null, { status: 204 }) 61 } 62 63 return new Response('Invalid request', { status: 422 }) 64 }, 65 async onMessage (message, ws, party) { 66 const messageStr = message.toString() 67 68 // 8. handle clearing votes from slide deck 69 if (messageStr === 'clear') { 70 if (party.id === 'feedback') { 71 await party.storage.put('feedback', []) 72 party.broadcast(`feedback:[]`) 73 } 74 else { 75 await party.storage.put('count', 0) 76 party.broadcast(`count:0`) 77 } 78 } 79 80 // 7. handle reaction messages 81 if (party.id === 'reactions' && messageStr.startsWith('reaction:')) { 82 const emoji = messageStr.replace('reaction:', '') 83 84 // Validate emoji 85 if (!isValidEmoji(emoji)) { 86 return 87 } 88 89 // Store the reaction 90 await party.storage.transaction(async tx => { 91 const reactions = await tx.get<string[]>('reactions') || [] 92 reactions.push(emoji) 93 // Keep only the last 100 reactions 94 if (reactions.length > 100) reactions.shift() 95 await tx.put('reactions', reactions) 96 }) 97 98 // Broadcast to all clients 99 party.broadcast(messageStr) 100 } 101 }, 102} satisfies PartyKitServer 103 104const getCount = (room: Room) => room.storage.get<number>('count').then(r => r || 0)