2026-04-18 18:19:46 +08:00
|
|
|
import { pb } from './pocketbase'
|
|
|
|
|
import { awardPoints, deductPoints } from './points'
|
2026-04-18 18:51:23 +08:00
|
|
|
import { createNotification } from './notifications'
|
2026-04-18 18:19:46 +08:00
|
|
|
import type { Poll, PollOption, PollVote } from '@/types'
|
|
|
|
|
|
|
|
|
|
export async function createPoll(data: {
|
|
|
|
|
group: string
|
|
|
|
|
title: string
|
|
|
|
|
type?: 'option' | 'rollcall'
|
|
|
|
|
anonymous?: boolean
|
|
|
|
|
deadline?: string
|
|
|
|
|
maxParticipants?: number
|
|
|
|
|
options: string[]
|
|
|
|
|
}): Promise<Poll> {
|
|
|
|
|
const user = pb.authStore.model
|
|
|
|
|
if (!user) throw new Error('未登录')
|
|
|
|
|
|
|
|
|
|
const poll = await pb.collection('polls').create({
|
|
|
|
|
group: data.group,
|
|
|
|
|
title: data.title,
|
|
|
|
|
type: data.type || 'option',
|
|
|
|
|
anonymous: data.anonymous || false,
|
|
|
|
|
deadline: data.deadline || '',
|
|
|
|
|
maxParticipants: data.type === 'rollcall' ? data.maxParticipants : null,
|
|
|
|
|
status: 'active',
|
|
|
|
|
creator: user.id,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < data.options.length; i++) {
|
|
|
|
|
await pb.collection('poll_options').create({
|
|
|
|
|
poll: poll.id,
|
|
|
|
|
content: data.options[i],
|
|
|
|
|
order: i + 1,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-18 18:51:23 +08:00
|
|
|
// 给同群组其他成员发送通知
|
|
|
|
|
try {
|
|
|
|
|
const group = await pb.collection('groups').getOne(data.group)
|
|
|
|
|
const typeLabel = data.type === 'rollcall' ? '接龙报名' : '投票'
|
|
|
|
|
const otherMembers = (group.members || []).filter((id: string) => id !== user.id)
|
|
|
|
|
await Promise.all(
|
|
|
|
|
otherMembers.map((memberId: string) =>
|
|
|
|
|
createNotification({
|
|
|
|
|
user: memberId,
|
|
|
|
|
type: 'poll_new',
|
|
|
|
|
title: `新${typeLabel}`,
|
|
|
|
|
content: `${data.title}`,
|
|
|
|
|
relatedId: poll.id,
|
|
|
|
|
relatedType: 'poll',
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
} catch {
|
|
|
|
|
// 通知发送失败不影响主流程
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-18 18:19:46 +08:00
|
|
|
return poll as unknown as Poll
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function listPolls(
|
|
|
|
|
groupId: string,
|
|
|
|
|
status?: string
|
|
|
|
|
): Promise<Poll[]> {
|
|
|
|
|
let filter = `group="${groupId}"`
|
|
|
|
|
if (status) filter += ` && status="${status}"`
|
|
|
|
|
|
|
|
|
|
const result = await pb.collection('polls').getFullList({
|
|
|
|
|
filter,
|
|
|
|
|
sort: '-created',
|
|
|
|
|
expand: 'creator',
|
|
|
|
|
$autoCancel: false,
|
|
|
|
|
})
|
|
|
|
|
return result as unknown as Poll[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getPoll(pollId: string): Promise<Poll> {
|
|
|
|
|
const result = await pb.collection('polls').getOne(pollId, {
|
|
|
|
|
expand: 'creator',
|
|
|
|
|
})
|
|
|
|
|
return result as unknown as Poll
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getPollOptions(pollId: string): Promise<PollOption[]> {
|
|
|
|
|
const result = await pb.collection('poll_options').getFullList({
|
|
|
|
|
filter: `poll="${pollId}"`,
|
|
|
|
|
sort: 'order',
|
|
|
|
|
$autoCancel: false,
|
|
|
|
|
})
|
|
|
|
|
return result as unknown as PollOption[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getPollVotes(pollId: string): Promise<PollVote[]> {
|
|
|
|
|
const result = await pb.collection('poll_votes').getFullList({
|
|
|
|
|
filter: `poll="${pollId}"`,
|
|
|
|
|
expand: 'user,option',
|
|
|
|
|
$autoCancel: false,
|
|
|
|
|
})
|
|
|
|
|
return result as unknown as PollVote[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function votePoll(
|
|
|
|
|
pollId: string,
|
|
|
|
|
optionId: string
|
|
|
|
|
): Promise<PollVote> {
|
|
|
|
|
const user = pb.authStore.model
|
|
|
|
|
if (!user) throw new Error('未登录')
|
|
|
|
|
|
|
|
|
|
const poll = await pb.collection('polls').getOne(pollId)
|
|
|
|
|
if (poll.status !== 'active') {
|
|
|
|
|
throw new Error('投票已结束')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existing = await pb.collection('poll_votes').getList(1, 1, {
|
|
|
|
|
filter: `poll="${pollId}" && user="${user.id}"`,
|
|
|
|
|
})
|
|
|
|
|
if (existing.items.length > 0) {
|
|
|
|
|
throw new Error('你已经投过票了')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const vote = await pb.collection('poll_votes').create({
|
|
|
|
|
poll: pollId,
|
|
|
|
|
option: optionId,
|
|
|
|
|
user: user.id,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await awardPoints('vote', pollId)
|
|
|
|
|
|
|
|
|
|
return vote as unknown as PollVote
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
if (error?.response?.data?.poll || error?.message?.includes('unique')) {
|
|
|
|
|
throw new Error('你已经投过票了')
|
|
|
|
|
}
|
|
|
|
|
throw error
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function cancelVote(pollId: string): Promise<void> {
|
|
|
|
|
const user = pb.authStore.model
|
|
|
|
|
if (!user) throw new Error('未登录')
|
|
|
|
|
|
|
|
|
|
const poll = await pb.collection('polls').getOne(pollId)
|
|
|
|
|
if (poll.status !== 'active') {
|
|
|
|
|
throw new Error('投票已结束,无法取消')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existing = await pb.collection('poll_votes').getFullList({
|
|
|
|
|
filter: `poll="${pollId}" && user="${user.id}"`,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
for (const vote of existing) {
|
|
|
|
|
await pb.collection('poll_votes').delete(vote.id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await deductPoints('vote', pollId)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function settlePoll(pollId: string): Promise<Poll> {
|
|
|
|
|
const result = await pb.collection('polls').update(pollId, {
|
|
|
|
|
status: 'settled',
|
|
|
|
|
settledAt: new Date().toISOString(),
|
|
|
|
|
})
|
|
|
|
|
return result as unknown as Poll
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function updatePoll(pollId: string, data: {
|
|
|
|
|
title?: string
|
|
|
|
|
deadline?: string
|
|
|
|
|
maxParticipants?: number | null
|
|
|
|
|
}): Promise<Poll> {
|
|
|
|
|
const result = await pb.collection('polls').update(pollId, data)
|
|
|
|
|
return result as unknown as Poll
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function addPollOption(pollId: string, content: string): Promise<PollOption> {
|
|
|
|
|
const existing = await pb.collection('poll_options').getFullList({
|
|
|
|
|
filter: `poll="${pollId}"`,
|
|
|
|
|
sort: '-order',
|
|
|
|
|
$autoCancel: false,
|
|
|
|
|
})
|
|
|
|
|
const maxOrder = existing.reduce((max: number, o: any) => Math.max(max, o.order || 0), 0)
|
|
|
|
|
const result = await pb.collection('poll_options').create({
|
|
|
|
|
poll: pollId,
|
|
|
|
|
content,
|
|
|
|
|
order: maxOrder + 1,
|
|
|
|
|
})
|
|
|
|
|
return result as unknown as PollOption
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function updatePollOption(optionId: string, content: string): Promise<PollOption> {
|
|
|
|
|
const result = await pb.collection('poll_options').update(optionId, { content })
|
|
|
|
|
return result as unknown as PollOption
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function deletePollOption(optionId: string): Promise<void> {
|
|
|
|
|
await pb.collection('poll_options').delete(optionId)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getUserVote(pollId: string): Promise<PollVote | null> {
|
|
|
|
|
const user = pb.authStore.model
|
|
|
|
|
if (!user) return null
|
|
|
|
|
|
|
|
|
|
const result = await pb.collection('poll_votes').getList(1, 1, {
|
|
|
|
|
filter: `poll="${pollId}" && user="${user.id}"`,
|
|
|
|
|
expand: 'option',
|
|
|
|
|
$autoCancel: false,
|
|
|
|
|
})
|
|
|
|
|
return result.items.length > 0
|
|
|
|
|
? (result.items[0] as unknown as PollVote)
|
|
|
|
|
: null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function subscribePolls(
|
|
|
|
|
groupId: string,
|
|
|
|
|
callback: (data: any) => void
|
|
|
|
|
): Promise<() => void> {
|
|
|
|
|
await pb.collection('polls').subscribe('*', (data) => {
|
|
|
|
|
if (data.record?.group === groupId) {
|
|
|
|
|
callback(data)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
pb.collection('polls').unsubscribe('*')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function subscribePollVotes(
|
|
|
|
|
pollId: string,
|
|
|
|
|
callback: (data: any) => void
|
|
|
|
|
): Promise<() => void> {
|
|
|
|
|
await pb.collection('poll_votes').subscribe('*', (data) => {
|
|
|
|
|
if (data.record?.poll === pollId) {
|
|
|
|
|
callback(data)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
pb.collection('poll_votes').unsubscribe('*')
|
|
|
|
|
}
|
|
|
|
|
}
|