refactor(board): atomic delete + query limit + composable cleanup
Some checks failed
Test / vitest (push) Failing after 7m17s
Test / playwright (push) Has been skipped
Test / visual (push) Has been skipped
Test / Notify on failure (push) Successful in 1s

Delete uses findOneAndDelete with author match (no TOCTOU window);
existence check only runs on miss to distinguish 403 vs 404. Posts
list capped at 200. Drop unused resolveTagChannel and refreshParams;
route slack URL building through the composable's slackUrl helper.
This commit is contained in:
Jennie Robinson Faber 2026-04-15 12:47:53 +01:00
parent d1a1484daf
commit 28040f44f4
7 changed files with 30 additions and 54 deletions

View file

@ -86,6 +86,8 @@ const props = defineProps({
defineEmits(['edit', 'delete', 'confirm-delete', 'cancel-delete'])
const { slackUrl } = useBoardChannels()
const capitalizeAvatar = (str) => {
if (str.toLowerCase() === 'wtf') return 'WTF'
return str
@ -148,7 +150,7 @@ const slackLinks = computed(() => {
.map((c) => ({
id: c.slackChannelId,
name: c.slackChannelName || c.name || c.slackChannelId,
url: `https://gammaspace.slack.com/archives/${c.slackChannelId}`,
url: slackUrl(c.slackChannelId),
}))
})
</script>

View file

@ -1,7 +1,3 @@
/**
* Board Channels Composable
* Shared state + helpers for mapping board tags to Slack channels.
*/
export function useBoardChannels() {
const channels = useState('board.channels', () => [])
@ -11,15 +7,6 @@ export function useBoardChannels() {
return channels.value
}
function resolveTagChannel(tagSlugs = []) {
if (!tagSlugs?.length) return null
return (
channels.value.find((channel) =>
(channel.tagSlugs || []).some((slug) => tagSlugs.includes(slug))
) || null
)
}
function slackUrl(channelId) {
return `https://gammaspace.slack.com/archives/${channelId}`
}
@ -27,7 +14,6 @@ export function useBoardChannels() {
return {
channels: readonly(channels),
fetchChannels,
resolveTagChannel,
slackUrl,
}
}

View file

@ -1,7 +1,3 @@
/**
* Board Posts Composable
* Shared state + CRUD for board posts.
*/
export function useBoardPosts() {
const posts = useState('board.posts', () => [])
const loading = useState('board.loading', () => false)
@ -17,29 +13,29 @@ export function useBoardPosts() {
}
}
async function createPost(body, refreshParams = {}) {
async function createPost(body) {
const created = await $fetch('/api/board/posts', {
method: 'POST',
body,
})
await fetchPosts(refreshParams)
await fetchPosts()
return created
}
async function updatePost(id, body, refreshParams = {}) {
async function updatePost(id, body) {
const updated = await $fetch(`/api/board/posts/${id}`, {
method: 'PATCH',
body,
})
await fetchPosts(refreshParams)
await fetchPosts()
return updated
}
async function deletePost(id, refreshParams = {}) {
async function deletePost(id) {
const result = await $fetch(`/api/board/posts/${id}`, {
method: 'DELETE',
})
await fetchPosts(refreshParams)
await fetchPosts()
return result
}

View file

@ -1,13 +1,11 @@
<template>
<PageShell title="Bulletin Board" :subtitle="pageSubtitle">
<!-- Action bar -->
<div class="action-bar">
<button type="button" class="new-post-btn" @click="openNewForm">
+ New Post
</button>
</div>
<!-- Tags Drawer Toggle -->
<div v-if="cooperativeTags.length > 0" class="tags-drawer-toggle">
<button type="button" class="drawer-btn" @click="showTagsDrawer = !showTagsDrawer">
Tags...
@ -15,7 +13,6 @@
</button>
</div>
<!-- Tags Drawer -->
<div v-if="showTagsDrawer && cooperativeTags.length > 0" class="tags-drawer">
<div class="skills-bar">
<span class="tag-label">Filter:</span>
@ -40,7 +37,6 @@
</div>
</div>
<!-- Inline form -->
<div v-if="showForm" class="form-wrapper">
<BoardPostForm
:post="editingPost"
@ -50,7 +46,6 @@
/>
</div>
<!-- Content -->
<ClientOnly>
<div v-if="loading" class="loading-state">
<p>Loading board...</p>