Skip to content

feat: create message action#173

Merged
hmbanan666 merged 1 commit into
mainfrom
message-create
Sep 19, 2025
Merged

feat: create message action#173
hmbanan666 merged 1 commit into
mainfrom
message-create

Conversation

@hmbanan666
Copy link
Copy Markdown
Collaborator

@hmbanan666 hmbanan666 commented Sep 19, 2025

Summary by CodeRabbit

  • New Features

    • Compose and send messages to tickets via a drawer; lists refresh automatically with feedback.
    • Ticket cards highlight when a partner has replied.
    • Navigation shows a badge for tickets awaiting your response.
    • Item pages display created date/time and message count.
  • Style

    • Larger avatars in messages and views.
    • Softer button styles and added decorative imagery.
    • Removed bottom placeholder icons on some pages.
  • Chores

    • Added queue integration requiring new configuration.

@hmbanan666 hmbanan666 self-assigned this Sep 19, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Sep 19, 2025

Walkthrough

Introduces ticket message creation end-to-end: a new POST API for ticket messages, shared validation schema, queue plugin/connection with new env var and dependency, a form component and drawer UI on the ticket page, store/composable updates for unanswered ticket badges, and multiple minor UI tweaks (avatars, labels, icons, decorative image, timestamp formats, and removals of bottom icons).

Changes

Cohort / File(s) Summary
Queue & Config Integration
apps/atrium-telegram/.env.example, apps/atrium-telegram/server/plugins/02.queue.ts, apps/atrium-telegram/package.json
Adds QUEUE_URL env, initializes queue connection via Nitro plugin, and adds dependency @roll-stack/queue (workspace).
Ticket Message API
apps/atrium-telegram/server/api/ticket/id/[ticketId]/message.post.ts, apps/atrium-telegram/shared/services/ticket.ts
New POST endpoint to create ticket messages with validation; on success persists message and emits messageCreated event to queue. Exports runtime schema and type for message payload.
Frontend Message Creation
apps/atrium-telegram/app/components/form/CreateTicketMessage.vue, apps/atrium-telegram/app/pages/ticket/[ticketId]/index.vue
Adds message form component; integrates a drawer composer on ticket page; passes ticketId to form and message items; closes drawer on submit/success; adjusts “Show more” button style/icon.
Store & Navigation Badge
apps/atrium-telegram/app/stores/ticket.ts, apps/atrium-telegram/app/composables/useNavigation.ts
Adds computed ticketsWithoutAnswer to store and uses it to populate a badge count for the /ticket route.
Ticket UI Tweaks
apps/atrium-telegram/app/components/ticket/Card.vue, apps/atrium-telegram/app/components/ticket/Message.vue, apps/atrium-telegram/app/components/ticket/MessageFile.vue
Card: new indicator when last message is from ticket owner; adds hasAnswerFromUser; date format includes time. Message: avatar size set to lg. MessageFile: button variant changed to soft.
Page Layout Adjustments
apps/atrium-telegram/app/pages/flow/[itemId]/index.vue, apps/atrium-telegram/app/pages/index.vue, apps/atrium-telegram/app/pages/ticket/index.vue, apps/atrium-telegram/app/components/PageContainer.vue
Flow item: shows message count and created-at timestamp; avatars size lg. Index and ticket index pages: removed bottom centered icon blocks. PageContainer: adds decorative sushi-heart image.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant C as Ticket Page (Drawer + Form)
  participant API as POST /api/ticket/id/:ticketId/message
  participant Repo as repository.ticket
  participant Q as Queue (connection)
  participant S as Stores (ticket/user)

  U->>C: Open "Написать сообщение"
  C->>API: Submit { text } with Authorization
  API->>Repo: find(ticketId)
  Repo-->>API: ticket | 404
  API->>Repo: createMessage({ ticketId, userId, text })
  Repo-->>API: message
  API->>Q: emit messageCreated({ ticketId, ticketOwnerId, messageId, ... })
  API-->>C: { ok: true, result: message }
  par Refresh state
    C->>S: ticketStore.update()
    C->>S: userStore.update()
  end
  C->>C: Close drawer
Loading
sequenceDiagram
  participant Store as ticketStore
  participant Nav as useNavigation()
  Store-->>Nav: ticketsWithoutAnswer (computed)
  Nav-->>Nav: badge = length.toString()
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

A whisper in the queue goes beep,
I hop and stamp, no time for sleep.
A ticket sings, a message flies,
Drawers close with cheerful sighs.
Badges bloom—unanswered few—
I thump: “Delivered!”—off I zoom. 🐇✉️🧡

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat: create message action" is concise, follows conventional-commit style, and correctly summarizes the primary change in this PR — adding message-creation functionality (server endpoint, form/component, schema, and queue integration) for tickets in the atrium-telegram app, so it conveys the main intent to reviewers scanning history.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch message-create

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
23.6% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@hmbanan666 hmbanan666 merged commit e52e382 into main Sep 19, 2025
6 of 8 checks passed
@hmbanan666 hmbanan666 deleted the message-create branch September 19, 2025 13:13
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/atrium-telegram/app/components/ticket/Message.vue (1)

64-115: Type error risk: custom "condition" field isn't part of DropdownMenuItem

You’re adding a non-standard property (condition) to items typed as DropdownMenuItem, which likely fails typecheck and blocks CI. Define a local extended type to carry the condition flag.

Apply this diff:

-import type { DropdownMenuItem } from '@nuxt/ui'
+import type { DropdownMenuItem } from '@nuxt/ui'

+type MenuItem = DropdownMenuItem & { condition?: boolean }
...
-const items = computed<DropdownMenuItem[]>(() => {
-  const menuItems: DropdownMenuItem[] = [
+const items = computed<DropdownMenuItem[]>(() => {
+  const menuItems: MenuItem[] = [
...
-  return menuItems.filter((item) => item.condition)
+  return menuItems.filter((item) => item.condition)
})
apps/atrium-telegram/app/pages/ticket/[ticketId]/index.vue (1)

86-89: “Show last 10 messages” logic is reversed.

slice(0, n) shows oldest N. Use negative index to show the latest N.

-const messages = computed(() => ticket.value?.messages.slice(0, shownMessages.value))
+const messages = computed(() => ticket.value?.messages.slice(-shownMessages.value))
🧹 Nitpick comments (18)
apps/atrium-telegram/app/components/ticket/Message.vue (2)

79-81: Clipboard write should handle permission/errors

Wrap writeText in try/catch to avoid silent failures (denied permissions, insecure context).

Apply this diff:

-      onSelect: () => navigator.clipboard.writeText(message.value?.text ?? ''),
+      onSelect: async () => {
+        try {
+          await navigator.clipboard.writeText(message.value?.text ?? '')
+        } catch {
+          // Optionally notify the user
+        }
+      },

117-122: Open in new tab safely (prevent reverse‑tabnabbing)

Use noopener/noreferrer when opening external URLs.

Apply this diff:

-  window.open(fileUrl, '_blank')
+  window.open(fileUrl, '_blank', 'noopener,noreferrer')
apps/atrium-telegram/.env.example (1)

4-6: Document the required QUEUE_URL format

Since the plugin hard-fails when QUEUE_URL is missing, add a sample format to ease setup.

Apply this diff:

 # Queue
 QUEUE_URL=
+
+# e.g. amqp://user:pass@localhost:5672/vhost
+# Required by server/plugins/02.queue.ts at startup
apps/atrium-telegram/app/components/ticket/MessageFile.vue (1)

46-48: Typo in Russian copy: “файл” → “файл”

The string uses a combining mark (й). Replace with the single letter “й”.

Apply this diff:

-        label: 'Прикреплен файл',
+        label: 'Прикреплен файл',
apps/atrium-telegram/server/plugins/02.queue.ts (1)

8-13: Don’t hard‑fail app startup if QUEUE_URL is absent (gate by env or degrade gracefully)

Throwing aborts the whole service even for features not using the queue. Prefer skipping init in dev/test or behind an explicit flag, and add clearer error context.

Apply this diff:

 export default defineNitroPlugin(async () => {
-  if (!process.env.QUEUE_URL) {
-    throw new Error('QUEUE_URL is not defined')
-  }
-
-  await useCreateConnection(process.env.QUEUE_URL)
+  const url = process.env.QUEUE_URL
+  if (!url) {
+    console.warn('[queue] QUEUE_URL not set — skipping queue initialization')
+    return
+  }
+  try {
+    await useCreateConnection(url)
+    // console.info('[queue] connected')
+  } catch (err) {
+    console.error('[queue] connection failed', err)
+    throw err
+  }
 })
apps/atrium-telegram/app/components/PageContainer.vue (1)

6-12: Lazy-load decorative image

Small perf win: mark decorative image as lazy/async and non-draggable.

Apply this diff:

-        <img
+        <img
           src="/sushi-heart.svg"
           alt=""
           class="w-10 opacity-25 invert-50"
+          loading="lazy"
+          decoding="async"
+          draggable="false"
         >
apps/atrium-telegram/app/stores/ticket.ts (1)

13-14: Definition of “without answer” — should tickets with no messages count?

Current filter excludes tickets with no messages. If “unanswered” should include brand-new tickets, include null lastMessage.

Apply this diff if desired:

-  const ticketsWithoutAnswer = computed(() => tickets.value.filter((ticket) => ticket.lastMessage?.userId === ticket.userId))
+  const ticketsWithoutAnswer = computed(() =>
+    tickets.value.filter((t) => (t.lastMessage ? t.lastMessage.userId === t.userId : true)),
+  )
apps/atrium-telegram/app/pages/flow/[itemId]/index.vue (2)

16-30: Hardcoded messages count

“0” should be dynamic or hidden when unknown.

Suggested pattern:

-            <p>0</p>
+            <p>{{ item?.messagesCount ?? 0 }}</p>

Adjust to actual source of truth (store/API) if different.


51-53: Safer date parsing

Prefer parseISO to avoid Date() locale quirks.

Apply this diff:

-import { format } from 'date-fns'
+import { format, parseISO } from 'date-fns'
...
-          v-text="format(new Date(item.createdAt), 'd MMMM yyyy в HH:mm', { locale: ru })"
+          v-text="format(parseISO(item.createdAt), 'd MMMM yyyy в HH:mm', { locale: ru })"
apps/atrium-telegram/shared/services/ticket.ts (1)

3-6: Prevent whitespace-only messages; consider normalizing.

Schema allows " " x N (length ≥1). Either trim and re‑validate server‑side or enforce trimmed input.

Apply trimming in the API handler (recommended):

-    const data = createTicketMessageSchema(body)
+    const data = createTicketMessageSchema(body)
+    const text = data.text.trim()
+    if (!text) {
+      throw createError({ statusCode: 400, message: 'Text must not be empty' })
+    }
apps/atrium-telegram/app/components/ticket/Card.vue (1)

36-36: Optional: locale-aware time formatting.

If updatedAt is ISO, this is fine. Consider timezone awareness if server sends UTC.

apps/atrium-telegram/app/components/form/CreateTicketMessage.vue (3)

8-16: i18n consistency for label/placeholder.

Hardcoded RU strings — use $t like the button.

-    <UFormField label="Ваше сообщение" name="text">
+    <UFormField :label="$t('ticket.message.label')" name="text">
...
-        placeholder="Не торопись, осмотрись..."
+        :placeholder="$t('ticket.message.placeholder')"

18-28: Disable while submitting; show loading.

Prevents double‑submits and improves UX.

-    <UButton
+    <UButton
       type="submit"
       variant="solid"
       color="secondary"
       size="xl"
       icon="i-lucide-send"
       block
       class="mt-3"
-      :disabled="!state.text"
+      :disabled="!state.text || loading"
+      :loading="loading"
       :label="$t('common.send')"
     />

45-47: Initialize state as empty string for better v-model ergonomics.

Avoids undefined binding in textarea.

-const state = ref<Partial<CreateTicketMessage>>({
-  text: undefined,
-})
+const state = ref<Partial<CreateTicketMessage>>({ text: '' })
apps/atrium-telegram/server/api/ticket/id/[ticketId]/message.post.ts (2)

30-40: Use trimmed text and return 201.

Minor polish.

-    const message = await repository.ticket.createMessage({
+    const message = await repository.ticket.createMessage({
       ticketId,
       userId: event.context.user.id,
-      text: data.text,
+      text: data.text,
     })
...
-    return {
+    setResponseStatus(event, 201)
+    return {
       ok: true,
       result: message,
     }

42-51: Defensive defaults for user name/surname.

Avoid undefined in emitted payload.

-      userName: event.context.user.name,
-      userSurname: event.context.user.surname,
+      userName: event.context.user.name ?? '',
+      userSurname: event.context.user.surname ?? '',
apps/atrium-telegram/app/pages/ticket/[ticketId]/index.vue (1)

91-91: Drawer default state.

If you want the composer discoverable, consider prompting via FAB instead of relying on the drawer default false. As-is, fine.

apps/atrium-telegram/app/composables/useNavigation.ts (1)

24-24: Hide zero badge and clamp large counts.

NavigationRoute declares badge?: string in apps/atrium-telegram/shared/types/index.ts — assigning undefined is allowed. Replace with:

-      badge: ticketStore.ticketsWithoutAnswer.length.toString(),
+      badge: ticketStore.ticketsWithoutAnswer.length
+        ? Math.min(99, ticketStore.ticketsWithoutAnswer.length).toString()
+        : undefined,
  • Apply the same pattern to the other badges (flowStore, taskStore) to hide zeros and cap counts at 99.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2719478 and e3139c4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (16)
  • apps/atrium-telegram/.env.example (1 hunks)
  • apps/atrium-telegram/app/components/PageContainer.vue (1 hunks)
  • apps/atrium-telegram/app/components/form/CreateTicketMessage.vue (1 hunks)
  • apps/atrium-telegram/app/components/ticket/Card.vue (3 hunks)
  • apps/atrium-telegram/app/components/ticket/Message.vue (1 hunks)
  • apps/atrium-telegram/app/components/ticket/MessageFile.vue (1 hunks)
  • apps/atrium-telegram/app/composables/useNavigation.ts (2 hunks)
  • apps/atrium-telegram/app/pages/flow/[itemId]/index.vue (2 hunks)
  • apps/atrium-telegram/app/pages/index.vue (0 hunks)
  • apps/atrium-telegram/app/pages/ticket/[ticketId]/index.vue (3 hunks)
  • apps/atrium-telegram/app/pages/ticket/index.vue (0 hunks)
  • apps/atrium-telegram/app/stores/ticket.ts (2 hunks)
  • apps/atrium-telegram/package.json (1 hunks)
  • apps/atrium-telegram/server/api/ticket/id/[ticketId]/message.post.ts (1 hunks)
  • apps/atrium-telegram/server/plugins/02.queue.ts (1 hunks)
  • apps/atrium-telegram/shared/services/ticket.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • apps/atrium-telegram/app/pages/ticket/index.vue
  • apps/atrium-telegram/app/pages/index.vue
🧰 Additional context used
🧬 Code graph analysis (4)
apps/atrium-telegram/app/composables/useNavigation.ts (1)
apps/atrium-telegram/app/stores/ticket.ts (1)
  • useTicketStore (10-47)
apps/atrium-telegram/server/plugins/02.queue.ts (1)
packages/queue/src/connection.ts (1)
  • useCreateConnection (15-20)
apps/atrium-telegram/server/api/ticket/id/[ticketId]/message.post.ts (1)
apps/atrium-telegram/shared/services/ticket.ts (1)
  • createTicketMessageSchema (3-5)
apps/atrium-telegram/app/stores/ticket.ts (1)
packages/database/src/tables.ts (1)
  • tickets (689-701)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (7)
apps/atrium-telegram/app/components/ticket/Message.vue (1)

4-4: Avatar sizing change looks good

Consistent with other components. No functional concerns.

apps/atrium-telegram/app/components/ticket/MessageFile.vue (1)

7-7: UI tweak acknowledged

Variant change to "soft" is fine; matches rest of the UI theme.

apps/atrium-telegram/app/pages/flow/[itemId]/index.vue (1)

39-44: Avatar sizing change looks good

Consistent with ticket message avatars.

apps/atrium-telegram/package.json (1)

20-21: New workspace dependency looks correct — local verification required

Sandbox verification failed: apps/atrium-telegram/package.json not found and pnpm -w cannot run outside a workspace. From the repo root run:
jq -r '.dependencies["@roll-stack/queue"]' apps/atrium-telegram/package.json
pnpm -w why @roll-stack/queue
Confirm @roll-stack/queue is a workspace member and that CI builds it.

apps/atrium-telegram/app/pages/ticket/[ticketId]/index.vue (2)

33-49: LGTM on drawer integration.

Wiring the composer and closing on submit/success looks good.


59-64: LGTM on button styling.

Consistent with the new messaging UX.

apps/atrium-telegram/server/api/ticket/id/[ticketId]/message.post.ts (1)

22-28: Authorization gap — restrict posting to ticket owner or privileged users.

File: apps/atrium-telegram/server/api/ticket/id/[ticketId]/message.post.ts (lines 22–28)

     const ticket = await repository.ticket.find(ticketId)
     if (!ticket) {
       throw createError({
         statusCode: 404,
         message: 'Ticket not found',
       })
     }
+    // Only owner or privileged users can post
+    const isOwner = ticket.userId === event.context.user.id
+    const isPrivileged = Array.isArray(event.context.user.roles) && event.context.user.roles.includes('support')
+    if (!isOwner && !isPrivileged) {
+      throw createError({ statusCode: 403, message: 'Forbidden' })
+    }

Confirm the intended authorization model (owner-only vs. support/privileged roles) and adjust the guard accordingly.

Comment on lines +49 to +72
async function onSubmit(event: FormSubmitEvent<CreateTicketMessage>) {
emit('submitted')

try {
await $fetch(`/api/ticket/id/${ticketId}/message`, {
method: 'POST',
headers: {
Authorization: `tma ${userStore.initDataRaw}`,
},
body: event.data,
})

await Promise.all([
ticketStore.update(),
userStore.update(),
])

vibrate('success')
emit('success')
} catch (error) {
console.error(error)
vibrate('error')
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Trim input, guard whitespace, reset state, and add loading flag.

Prevents whitespace‑only posts and cleans up after success.

+const loading = ref(false)
 async function onSubmit(event: FormSubmitEvent<CreateTicketMessage>) {
   emit('submitted')
 
   try {
+    loading.value = true
+    const text = event.data.text.trim()
+    if (!text) {
+      vibrate('error')
+      return
+    }
     await $fetch(`/api/ticket/id/${ticketId}/message`, {
       method: 'POST',
       headers: {
         Authorization: `tma ${userStore.initDataRaw}`,
       },
-      body: event.data,
+      body: { text },
     })
 
     await Promise.all([
       ticketStore.update(),
       userStore.update(),
     ])
 
     vibrate('success')
     emit('success')
   } catch (error) {
     console.error(error)
     vibrate('error')
   }
+  finally {
+    loading.value = false
+    state.value.text = ''
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function onSubmit(event: FormSubmitEvent<CreateTicketMessage>) {
emit('submitted')
try {
await $fetch(`/api/ticket/id/${ticketId}/message`, {
method: 'POST',
headers: {
Authorization: `tma ${userStore.initDataRaw}`,
},
body: event.data,
})
await Promise.all([
ticketStore.update(),
userStore.update(),
])
vibrate('success')
emit('success')
} catch (error) {
console.error(error)
vibrate('error')
}
}
const loading = ref(false)
async function onSubmit(event: FormSubmitEvent<CreateTicketMessage>) {
emit('submitted')
try {
loading.value = true
const text = event.data.text.trim()
if (!text) {
vibrate('error')
return
}
await $fetch(`/api/ticket/id/${ticketId}/message`, {
method: 'POST',
headers: {
Authorization: `tma ${userStore.initDataRaw}`,
},
body: { text },
})
await Promise.all([
ticketStore.update(),
userStore.update(),
])
vibrate('success')
emit('success')
} catch (error) {
console.error(error)
vibrate('error')
} finally {
loading.value = false
state.value.text = ''
}
}
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/form/CreateTicketMessage.vue around lines
49 to 72, the submit handler should trim the input, block whitespace-only
messages, manage a loading flag, and reset form state after success: before
sending, set a local loading=true; trim the relevant text fields on event.data
(or create a trimmed copy) and if the trimmed message is empty, set
loading=false and abort (or emit a validation error); proceed to call the API
with the trimmed payload; in a finally block set loading=false; on success
clear/reset the form model/state and emit 'success' (keep vibrate calls as-is);
ensure errors still log and vibrate('error').

Comment on lines +6 to +15
<div v-if="hasAnswerFromUser" class="flex flex-row items-center gap-1.5 text-error">
<UIcon
name="i-lucide-pointer"
class="size-8 motion-translate-y-loop-25 motion-preset-seesaw motion-duration-2000"
/>
<p class="max-w-22 text-sm/4 font-bold">
Есть ответ от партнера
</p>
</div>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Wrong condition for “Ответ от партнера”.

The flag triggers when last message is by the ticket owner, not the partner. Invert the check.

Apply with the script change below (see Lines 47–52).

🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/ticket/Card.vue around lines 6–15 (and
adjust the related logic at lines 47–52), the template currently shows "Есть
ответ от партнера" when hasAnswerFromUser is true; invert that check so the UI
is shown when the last message is from the partner (e.g., use !hasAnswerFromUser
or a proper hasAnswerFromPartner flag). Update the logic at lines 47–52 where
the last-message author is evaluated to set the boolean true when the author is
the partner (or rename the computed flag to reflect partner ownership) and
adjust all usages accordingly to maintain consistent naming.

Comment on lines +47 to 52
const { ticket } = defineProps<{
ticket: TicketWithData
}>()

const hasAnswerFromUser = computed(() => ticket.lastMessage?.userId === ticket.userId)
</script>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prop destructuring drops reactivity; and the condition should be “!==”.

Destructuring defineProps without toRefs makes ticket non‑reactive. Also fix partner reply logic and name accordingly.

-const { ticket } = defineProps<{
-  ticket: TicketWithData
-}>()
-
-const hasAnswerFromUser = computed(() => ticket.lastMessage?.userId === ticket.userId)
+const props = defineProps<{ ticket: TicketWithData }>()
+const { ticket } = toRefs(props)
+
+const hasAnswerFromPartner = computed(
+  () => ticket.value.lastMessage?.userId !== ticket.value.userId
+)

And in template:

-<div v-if="hasAnswerFromUser" ...
+<div v-if="hasAnswerFromPartner" ...

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/ticket/Card.vue around lines 47–52, the
current destructuring of defineProps makes ticket non‑reactive and the computed
uses the wrong equality; stop destructuring props so reactivity is preserved
(e.g. const props = defineProps<{ ticket: TicketWithData }>(); then create a
reactive ref via toRef(props, 'ticket') or use toRefs), rename the computed to
reflect partner reply (e.g. hasAnswerFromPartner) and change the condition to
!== (ticket.lastMessage?.userId !== ticket.userId), and update template bindings
to use the ref appropriately so reactivity works.

Comment on lines +6 to +15
export default defineEventHandler(async (event) => {
try {
const ticketId = getRouterParam(event, 'ticketId')
if (!ticketId) {
throw createError({
statusCode: 400,
message: 'Id is required',
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Authenticate request early.

Guard missing user; otherwise event.context.user.id will throw.

 export default defineEventHandler(async (event) => {
   try {
+    if (!event.context.user) {
+      throw createError({ statusCode: 401, message: 'Unauthorized' })
+    }
     const ticketId = getRouterParam(event, 'ticketId')
     if (!ticketId) {
       throw createError({
         statusCode: 400,
         message: 'Id is required',
       })
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export default defineEventHandler(async (event) => {
try {
const ticketId = getRouterParam(event, 'ticketId')
if (!ticketId) {
throw createError({
statusCode: 400,
message: 'Id is required',
})
}
export default defineEventHandler(async (event) => {
try {
if (!event.context.user) {
throw createError({ statusCode: 401, message: 'Unauthorized' })
}
const ticketId = getRouterParam(event, 'ticketId')
if (!ticketId) {
throw createError({
statusCode: 400,
message: 'Id is required',
})
}
🤖 Prompt for AI Agents
In apps/atrium-telegram/server/api/ticket/id/[ticketId]/message.post.ts around
lines 6–15, the handler accesses event.context.user.id without verifying the
user exists; add an early authentication guard: check event.context &&
event.context.user (or event.context.user) immediately after retrieving ticketId
and before any usage, and if missing throw a 401/Unauthorized error via
createError; then safely read and use user.id from a local variable. Ensure the
guard runs before any other logic to prevent runtime exceptions.

Comment on lines +16 to +21
const body = await readBody(event)
const data = createTicketMessageSchema(body)
if (data instanceof type.errors) {
throw data
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Validate after trimming; reject whitespace-only.

Current schema accepts spaces. Normalize and re‑check.

-    const body = await readBody(event)
-    const data = createTicketMessageSchema(body)
+    const body = await readBody(event)
+    const data = createTicketMessageSchema(body)
+    data.text = data.text.trim()
+    if (!data.text) {
+      throw createError({ statusCode: 400, message: 'Text must not be empty' })
+    }
     if (data instanceof type.errors) {
       throw data
     }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/atrium-telegram/server/api/ticket/id/[ticketId]/message.post.ts around
lines 16 to 21, the code validates the raw body but doesn't trim string fields
first so inputs of only whitespace pass; before calling
createTicketMessageSchema, normalize the incoming body by trimming all string
fields (e.g., body.message = body.message?.trim()) and then re-run validation,
and update the schema to reject empty strings (or add a post-validate check) so
that whitespace-only values fail validation and the handler throws an error as
intended.

Comment on lines +57 to +60
} catch (error) {
throw errorResolver(error)
}
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Potential missing import for errorResolver.

Unless auto‑imported, this will throw ReferenceError.

If missing, add the import with the correct path:

+import { errorResolver } from '~/server/utils/errorResolver' // adjust path if different

🏁 Script executed:

#!/bin/bash
# Verify errorResolver is globally/auto imported somewhere
rg -n "errorResolver" -C2 apps/atrium-telegram/server | sed -n '1,200p'

Length of output: 7620


🏁 Script executed:

#!/bin/bash
# Display the top of the ticket message handler to check for errorResolver import
sed -n '1,30p' apps/atrium-telegram/server/api/ticket/id/\[ticketId\]/message.post.ts

Length of output: 836


Add missing import for errorResolver

apps/atrium-telegram/server/api/ticket/id/[ticketId]/message.post.ts calls errorResolver in the catch but does not import it — add at the top:

import { errorResolver } from '../../../../utils/error'

🤖 Prompt for AI Agents
In apps/atrium-telegram/server/api/ticket/id/[ticketId]/message.post.ts around
lines 57-60, the catch block calls errorResolver but the module is not imported;
add the import statement at the top of the file: import { errorResolver } from
'../../../../utils/error' so the function is available for use in the catch
block.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant