Skip to content

feat: new components and visual#92

Merged
hmbanan666 merged 2 commits into
mainfrom
components
Aug 26, 2025
Merged

feat: new components and visual#92
hmbanan666 merged 2 commits into
mainfrom
components

Conversation

@hmbanan666
Copy link
Copy Markdown
Collaborator

@hmbanan666 hmbanan666 commented Aug 26, 2025

Summary by CodeRabbit

  • New Features

    • Task management: view lists, create/edit tasks and projects, focus/unfocus tasks, and complete tasks with a report.
    • “Today vs All” switch with helpful toasts and dynamic counts.
    • Rich task cards with actions, dates, avatars, and modals.
    • Member avatar popovers and quick actions in list headers.
    • User avatar upload modal.
    • Authentication guard with a dedicated “No access” page.
  • Improvements

    • Home updated to “Пространство” with a personalized header and responsive grid.
    • Periodic data refresh for tasks and user presence.
  • Style

    • New theme tokens; adjusted scrolling behavior to browser defaults.

@hmbanan666 hmbanan666 self-assigned this Aug 26, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 26, 2025

Caution

Review failed

The pull request is closed.

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9a8149b and 2da8f7a.

📒 Files selected for processing (5)
  • apps/atrium-telegram/app/app.config.ts (0 hunks)
  • apps/atrium-telegram/app/assets/css/styles.css (3 hunks)
  • apps/atrium-telegram/app/components/CreateCard.vue (1 hunks)
  • apps/atrium-telegram/app/components/TaskCard.vue (1 hunks)
  • apps/atrium-telegram/app/components/TaskList.vue (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch components

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 29

🧹 Nitpick comments (58)
apps/atrium-telegram/server/api/user/list/staff.get.ts (1)

12-12: Apply projection and pagination to staff listing to reduce payload and protect PII.

Mirror the improvements suggested for the general list endpoint: explicit field selection, paging, and optional search.

-  return repository.user.findStaff()
+  const { page = '1', limit = '20', q } = getQuery(event)
+  const pageNum = Math.max(1, Number(page) || 1)
+  const limitNum = Math.min(100, Math.max(1, Number(limit) || 20))
+  return repository.user.findStaff({
+    page: pageNum,
+    limit: limitNum,
+    search: typeof q === 'string' ? q : undefined,
+    select: ['id', 'username', 'firstName', 'lastName', 'avatarUrl', 'role']
+  })
apps/atrium-telegram/app/components/Loader.vue (1)

2-6: Nice minimalist loader; add minor a11y and UX attributes.

Since the image is decorative, keep alt="" but also mark it hidden from the accessibility tree and avoid unnecessary drag interactions. Async decode is fine for small SVGs.

   <img
     src="/sushi-heart.svg"
-    alt=""
-    class="w-12 grayscale-100 opacity-10 motion-preset-pulse motion-duration-1000"
+    alt=""
+    aria-hidden="true"
+    decoding="async"
+    draggable="false"
+    class="w-12 grayscale-100 opacity-10 motion-preset-pulse motion-duration-1000"
   >

Optional: consider inlining the SVG (or using an Icon component) if you need to tint/animate paths without extra requests.

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

14-16: Forward the button click to the parent.

Right now, parents can’t listen to CreateCard’s click intent. Re-emit the button click for better composability.

 <script setup lang="ts">
-defineProps<{ label: string, icon: string }>()
+const props = defineProps<{ label: string; icon: string }>()
+const emit = defineEmits<{
+  (e: 'click', evt: MouseEvent): void
+}>()
 </script>

And in the template:

-    <UButton
+    <UButton
       size="md"
       variant="subtle"
       color="neutral"
       :label="label"
+      @click="emit('click', $event)"
     />
apps/atrium-telegram/app/pages/no-auth.vue (2)

3-6: Move strings to i18n and set a page title.

Avoid hardcoding Russian strings in the template; use i18n keys and define a head title for better UX/SEO.

 <template>
   <PageContainer :back="false">
-    <h1 class="text-2xl">
-      Нет доступа!
-    </h1>
-    <p>Напишите в поддержку.</p>
+    <h1 class="text-2xl">
+      {{ t('noAuth.title') }}
+    </h1>
+    <p>{{ t('noAuth.support') }}</p>
   </PageContainer>
 </template>
+
+<script setup lang="ts">
+const { t } = useI18n()
+useHead({ title: t('noAuth.pageTitle') })
+</script>

Suggested keys:

  • noAuth.title: "Нет доступа!"
  • noAuth.support: "Напишите в поддержку."
  • noAuth.pageTitle: "Нет доступа"

2-2: Consider adding a support action (link or button).

Offering a direct link/button to open support (Telegram, mailto, or internal support route) reduces friction for blocked users.

If you share your preferred support destination, I can propose a small CTA snippet.

apps/atrium-telegram/server/api/task/id/[taskId]/index.delete.ts (1)

21-23: Consider 204 No Content for delete success (nit)

Returning { ok: true } is fine, but DELETE typically responds with 204. If you prefer conventional REST semantics, you can do:

-    return { ok: true }
+    setResponseStatus(event, 204)
+    return null
packages/database/src/types.ts (1)

10-20: Avoid duplicate type sources for UserGender across packages

There’s another UserGender alias derived from a schema in apps/web-app/shared/services/common.ts (Line 6). Two parallel definitions can drift.

Options:

  • Re-export DB types to the app: import type { UserGender } from '@roll-stack/database' and use that in the web app.
  • Or, derive the DB union from the shared schema so there is exactly one source of truth.

Would you like me to propose a small refactor PR to unify these?

apps/atrium-telegram/app/stores/user.ts (5)

29-36: Type the /api/auth/me response and keep $fetch strongly typed

Currently data is any, which weakens safety when assigning to refs. Use generics on $fetch so mismatches are caught at compile-time.

-      const data = await $fetch('/api/auth/me', {
+      const data = await $fetch<User>('/api/auth/me', {
         headers: {
           Authorization: `tma ${initDataRaw.value}`,
         },
       })

Also applies to: 50-52


64-84: Type the /api/user/list response and narrow staff filter

Same as above: add a generic to $fetch. Also, the filter relies on truthiness of name/surname; keep it but make the shape explicit with a typed array.

-      const data = await $fetch('/api/user/list', {
+      const data = await $fetch<UserWithData[]>('/api/user/list', {
         headers: {
           Authorization: `tma ${initDataRaw.value}`,
         },
       })
       if (!data) {
         return
       }

-      staff.value = data.filter((user) => user.type === 'staff' && user.isActive && user.name && user.surname)
+      staff.value = data.filter(
+        (user) => user.type === 'staff' && user.isActive && !!user.name && !!user.surname
+      )
       users.value = data

Confirm that /api/user/list actually returns focusedTask when present. If not, either drop that field from UserWithData or update the API.


92-97: DRY headers and avoid repeating Authorization construction

Authorization header construction is duplicated across calls. Factor it into a small helper to reduce mistakes.

-      await $fetch(`/api/user/id/${id.value}/online`, {
+      await $fetch(`/api/user/id/${id.value}/online`, {
         method: 'POST',
-        headers: {
-          Authorization: `tma ${initDataRaw.value}`,
-        },
+        headers: authHeaders(),
       })

Add this helper near the top of the store:

function authHeaders() {
  return { Authorization: `tma ${initDataRaw.value}` }
}

22-24: Avoid “undefined undefined” in fullName (nit)

Minor polish: trim and skip empties.

-  const fullName = computed(() => {
-    return `${name.value} ${surname.value}`
-  })
+  const fullName = computed(() =>
+    [name.value, surname.value].filter(Boolean).join(' ').trim()
+  )

52-61: Prefer status-based handling over message string parsing

Checking error.message.includes('401')/'404' is brittle. If you’re using ofetch under the hood, narrow by status where possible.

} catch (error: any) {
  const status = error?.status || error?.response?.status
  if (status === 401) {
    // No session
  } else if (status === 404) {
    // Not found
  }
}

If $fetch here is Nuxt’s ofetch, we can type FetchError and pattern-match reliably. Do you want me to wire that up?

apps/atrium-telegram/i18n/locales/ru-RU.json (1)

8-8: Check product wording for “home”.

“Пространство” may read a bit abstract in Russian UI. If this tab represents a workspace/home dashboard, consider “Рабочее пространство” or keep “Домой/Главная” for clarity. If “Space” is a branded term, current wording is fine—just confirm intent with product/design.

apps/atrium-telegram/server/api/auth/me.get.ts (1)

13-13: Await DB call to keep errorResolver effective; optionally restore 404 on missing user.

Without await, a rejection from repository.user.find won’t be caught by this try/catch, bypassing errorResolver. Also consider returning 404 if the user record is missing.

Apply:

-    return repository.user.find(user.id)
+    const dbUser = await repository.user.find(user.id)
+    if (!dbUser) {
+      throw createError({ statusCode: 404, message: 'User not found' })
+    }
+    return dbUser
apps/atrium-telegram/server/api/task/id/[taskId]/focus.delete.ts (1)

24-36: Authorization check: consider idempotency and focus ownership.

Current logic forbids clearing focus unless the user is the performer, but doesn’t verify that the given taskId is actually the one currently focused by the user. Two suggestions:

  • Make the operation idempotent: if the user isn’t focusing this task, return { ok: true } without error.
  • Alternatively, enforce that user.focusedTaskId === task.id before clearing and return 409 if it isn’t.

Example (idempotent):

-    const task = await repository.task.find(taskId)
+    const task = await repository.task.find(taskId)
     if (!task) {
       throw createError({
         statusCode: 404,
         message: 'Task not found',
       })
     }
-    if (task.performerId !== user.id) {
+    if (task.performerId !== user.id) {
       throw createError({
         statusCode: 403,
         message: 'You are not the performer of this task',
       })
     }
+    // If user isn't actually focusing this task, no-op for idempotency
+    const freshUser = await repository.user.find(user.id)
+    if (!freshUser?.focusedTaskId || freshUser.focusedTaskId !== task.id) {
+      return { ok: true }
+    }
apps/atrium-telegram/server/middleware/01.auth.ts (1)

24-26: Validate Authorization scheme and handle malformed headers.

Currently any “ ” passes. Explicitly require tma (Telegram Mini Apps) scheme and trim payload.

Apply:

-    const [_, authData] = token.split(' ')
-    if (!authData) {
+    const [scheme, authData] = token.split(' ')
+    if (!authData || !/^tma$/i.test(scheme)) {
       return null
     }
apps/atrium-telegram/app/assets/css/styles.css (1)

1-9: UI tokens: consider fallbacks and distinct neutrals.

Good abstraction. Two tweaks:

  • Provide CSS var fallbacks for non-TG contexts to prevent empty colors.
  • Map 400/500/600 to distinct values (even if initially equal) to keep room for future contrast tuning.

Apply:

-  --ui-secondary: var(--tg-theme-link-color);
-  --ui-border: var(--tg-theme-section-separator-color);
-  --ui-text: var(--tg-theme-text-color);
+  --ui-secondary: var(--tg-theme-link-color, #168dcd);
+  --ui-border: var(--tg-theme-section-separator-color, #e7e7e7);
+  --ui-text: var(--tg-theme-text-color, #000000);
@@
-  --ui-color-neutral-400: var(--tg-theme-subtitle-text-color);
-  --ui-color-neutral-500: var(--tg-theme-subtitle-text-color);
-  --ui-color-neutral-600: var(--tg-theme-subtitle-text-color);
+  --ui-color-neutral-400: var(--tg-theme-subtitle-text-color, #9e9e9e);
+  --ui-color-neutral-500: var(--tg-theme-subtitle-text-color, #8a8a8a);
+  --ui-color-neutral-600: var(--tg-theme-subtitle-text-color, #757575);
apps/atrium-telegram/app/components/modal/UploadUserAvatar.vue (1)

4-7: Consider listening to a single form event or guard against double close.

If FormUploadUserAvatar emits both submitted and success sequentially, overlay.closeAll will be called twice. Prefer a single event (typically success) or a one-time guarded handler.

Example:

-      <FormUploadUserAvatar
-        @submitted="overlay.closeAll"
-        @success="overlay.closeAll"
-      />
+      <FormUploadUserAvatar
+        @success="overlay.closeAll"
+      />

Or:

// script setup
let closed = false
const onDone = () => { if (!closed) { closed = true; overlay.closeAll() } }
apps/atrium-telegram/app/app.vue (3)

52-54: Fix interval type for browser compatibility.

Typing the handle as NodeJS.Timeout often clashes in browser builds. Use ReturnType.

-let interval: NodeJS.Timeout
+let interval: ReturnType<typeof setInterval>

61-67: Harden periodic updates: avoid overlap and swallow rejections.

If a cycle takes >30s, callbacks can overlap; unhandled rejections will also bubble. Add an inFlight guard and use Promise.allSettled to prevent noisy errors.

-onMounted(async () => {
+onMounted(async () => {
   await Promise.all([
     user.updateOnline(),
     user.update(),
     task.update(),
   ])

-  interval = setInterval(async () => {
-    await Promise.all([
-      user.updateOnline(),
-      user.update(),
-      task.update(),
-    ])
-  }, 30000)
+  let inFlight = false
+  interval = setInterval(async () => {
+    if (inFlight) return
+    inFlight = true
+    try {
+      await Promise.allSettled([
+        user.updateOnline(),
+        user.update(),
+        task.update(),
+      ])
+    } finally {
+      inFlight = false
+    }
+  }, 30000)
 })

46-49: Consider moving the auth guard into a route/global middleware.

Putting navigateTo in app.vue runs on every boot/SSR and couples auth to the root component. A middleware keeps the root clean and centralizes auth rules.

apps/atrium-telegram/app/components/TasksTodaySwitch.vue (2)

7-7: Remove default-value when using v-model.

USwitch should be either controlled (v-model) or uncontrolled (default-value), not both. Keeping both can cause state divergence.

-    :default-value="taskStore.isTodayOnly"

6-13: Localize user-facing strings and consider using a watcher instead of @change.

  • The label and toast texts are hardcoded in Russian; use i18n keys for consistency with the rest of the app.
  • Depending on USwitch’s event model, @change may not fire in all cases; a watch on taskStore.isTodayOnly is more reliable.

Example (label):

-    :label="taskStore.isTodayOnly ? 'Сегодня' : 'Все задачи'"
+    :label="taskStore.isTodayOnly ? $t('tasks.today') : $t('tasks.all')"

Example (watch):

watch(() => taskStore.isTodayOnly, (val) => {
  // show toasts...
})
apps/atrium-telegram/app/components/modal/CompleteTask.vue (1)

6-7: Same double-close consideration as other modals.

If the form emits both events, closeAll will run twice. Prefer a single event or a guarded handler as noted in UploadUserAvatar.vue.

apps/atrium-telegram/app/components/modal/CreateTask.vue (2)

4-9: Close only on success to avoid premature modal dismissal

Closing the modal on both submitted and success can hide server-side validation errors if submitted fires before the API settles. Prefer closing only on success (or add error handling to keep it open on failures).

Apply this diff:

       <FormCreateTask
         :performer-id="performerId"
         :list-id="listId"
-        @submitted="overlay.closeAll"
-        @success="overlay.closeAll"
+        @success="overlay.closeAll"
       />

20-21: Avoid blanket overlay.closeAll; close only this modal if supported

If your overlay API supports closing the current modal instance (e.g., overlay.close(id) or a provided close() in slot props), use that instead of closeAll to avoid inadvertently closing other stacked overlays.

Can you confirm whether useOverlay exposes a per-instance close method in this codebase? If yes, I can propose a targeted patch.

apps/atrium-telegram/server/api/task/list/completed.get.ts (1)

13-16: Consistency: await repository call for clarity

Nit: Other handlers await repository calls; do the same here for consistency and to make intent explicit.

-    return repository.task.findAll()
+    return await repository.task.findAll(/* filters */)
apps/atrium-telegram/app/components/modal/UpdateTaskList.vue (1)

6-7: Close only on success to preserve error visibility

Same rationale as CreateTask.vue: avoid closing on submitted to prevent hiding errors.

-        @submitted="overlay.closeAll"
-        @success="overlay.closeAll"
+        @success="overlay.closeAll"
apps/atrium-telegram/app/components/modal/CreateTaskList.vue (2)

6-7: De-duplicate modal closing logic

Prefer closing only on success so network/server errors remain visible to the user.

-        @submitted="overlay.closeAll"
-        @success="overlay.closeAll"
+        @success="overlay.closeAll"

18-19: Consider targeted close vs. closeAll

If your overlay service supports closing the current modal instance, use that to avoid affecting other overlays.

Happy to patch this once you confirm the overlay API (e.g., overlay.close(id) or a close function provided via slot props).

apps/atrium-telegram/server/api/task/list/id/[listId].delete.ts (2)

38-48: Prevent partial updates with a transaction

Archiving the list and then the chat in separate operations risks partial state if the second call fails. Wrap both in a transaction if your repository supports it (e.g., Prisma’s $transaction or an equivalent).

Example (adapt to your data layer):

-    // Archive, not delete
-    await repository.task.updateList(listId, {
-      isArchived: true,
-    })
-
-    // Archive chat
-    if (list.chatId) {
-      await repository.chat.update(list.chatId, {
-        isArchived: true,
-      })
-    }
+    // Archive list and related chat atomically
+    await repository.$transaction(async (tx) => {
+      await tx.task.updateList(listId, { isArchived: true })
+      if (list.chatId) {
+        await tx.chat.update(list.chatId, { isArchived: true })
+      }
+    })

If transactions aren’t available, consider best-effort compensation on failure of the second update.


50-52: Response shape/style (nit)

Returning { ok: true } is fine. Consider HTTP 204 No Content if no body is needed, or return the updated list to keep API responses informative. Not a blocker.

apps/atrium-telegram/server/api/task/list/index.post.ts (1)

29-63: Make chat/list/member creation atomic.

Chat creation, member inserts, and list creation are separate operations; a failure mid-way can leave orphans (e.g., a chat without a list or incomplete membership).

Wrap these ops in a single DB transaction (begin/commit/rollback) via your repository layer. I can provide a concrete transaction wrapper for the repository APIs if helpful.

apps/atrium-telegram/shared/utils/helpers.ts (3)

4-18: Pluralization logic looks correct.

The Russian plural rules and edge cases (including 0 → “many”) are handled properly.

Consider typing idx as 0 | 1 | 2 (in ascending order) for readability:

-  let idx: 1 | 2 | 0
+  let idx: 0 | 1 | 2

20-28: Clarify fallback for 'unknown' gender.

Defaulting to masculine for unknown can be surprising. Either handle 'unknown' explicitly or document the choice.

Example:

-export function suffixByGender(word: [string, string], gender?: UserGender) {
+export function suffixByGender(word: [string, string], gender?: UserGender) {
   if (gender === 'male') {
     return word[0]
   } else if (gender === 'female') {
     return word[1]
   }
 
-  return word[0]
+  // Explicitly handle unknown/unspecified
+  return word[0] // or pick a neutral/default per product decision
}

If you want a neutral option, we could extend the signature to accept [masc, fem, neutral] and use it when gender is unknown.


30-39: Make getLocalizedResolution exhaustive and typed.

Without a default branch or return-type annotation, the function returns string | undefined. Tighten the typing and guard future additions.

Apply this diff:

-export function getLocalizedResolution(resolution: Resolution) {
+export function getLocalizedResolution(resolution: Resolution): string {
   switch (resolution) {
     case 'success':
       return 'Успешно выполнена'
     case 'failure':
       return 'Не выполнена'
     case 'unknown':
       return 'Не ясно, есть вопросы'
   }
+  // Exhaustive guard for future values
+  // @ts-expect-error -- compile-time check if Resolution widens
+  const _exhaustiveCheck: never = resolution
+  return 'Неизвестно'
}
apps/atrium-telegram/server/api/task/list/id/[listId].patch.ts (1)

56-80: Consider transactional updates for list + chat + members.

List update, chat update, and membership synchronization should succeed or fail together.

Wrap these operations in a single transaction at the repository layer to maintain consistency under failures or concurrency.

apps/atrium-telegram/server/api/task/id/[taskId]/focus.post.ts (2)

25-43: Guard flow is correct and tight.

404 for missing task, 403 when not the performer, and a check for already-focused are good defenses.

You might consider making “already focused” idempotent (return { ok: true }) or use 409 Conflict instead of 400. Not a blocker.


49-51: Avoid empty update payload; provide an explicit “touch”.

Passing {} to repository.task.update may be rejected by validators or be a no-op depending on implementation.

If supported, prefer a dedicated method:

-    // Updating time
-    await repository.task.update(task.id, {})
+    // Touch updatedAt without changing fields
+    await repository.task.touch?.(task.id) ?? repository.task.update(task.id, { updatedAt: new Date() as any })

If touch isn’t available and updatedAt is managed by the DB trigger, you can remove this call entirely.

apps/atrium-telegram/app/stores/task.ts (4)

19-26: Guard missing Authorization token and type the fetch.

When initDataRaw.value is undefined, you’ll send Authorization: "tma undefined". Short-circuit to avoid noisy 401s and type the response.

Apply this diff:

-  async function update() {
+  async function update() {
     try {
-      const data = await $fetch('/api/task/list', {
+      if (!initDataRaw.value) {
+        lists.value = []
+        return
+      }
+      const data = await $fetch<TaskListWithData[]>('/api/task/list', {
         headers: {
           Authorization: `tma ${initDataRaw.value}`,
         },
       })
       if (!data) {
         return
       }

30-40: Harden error handling; avoid message substring checks.

Parsing error.message for "401"/"404" is brittle. Prefer status/statusCode.

Apply this diff:

-    } catch (error) {
-      if (error instanceof Error) {
-        if (error.message.includes('401')) {
-          // No session
-        }
-        if (error.message.includes('404')) {
-          // Not found
-        }
-      }
-    }
+    } catch (error: any) {
+      const status = error?.statusCode ?? error?.status
+      if (status === 401) {
+        // No session
+        return
+      }
+      if (status === 404) {
+        // Not found
+        return
+      }
+      console.error(error)
+    }

43-54: Refresh lists after focusing to keep UI in sync.

After successfully focusing a task, the local state remains stale.

Apply this diff:

   async function setAsFocused(taskId: string) {
     try {
       await $fetch(`/api/task/id/${taskId}/focus`, {
         method: 'POST',
         headers: {
           Authorization: `tma ${initDataRaw.value}`,
         },
       })
+      await update()
     } catch (error) {
       console.error(error)
     }
   }

56-67: Refresh lists after unfocusing as well.

Same reason as above.

Apply this diff:

   async function setAsUnfocused(taskId: string) {
     try {
       await $fetch(`/api/task/id/${taskId}/focus`, {
         method: 'DELETE',
         headers: {
           Authorization: `tma ${initDataRaw.value}`,
         },
       })
+      await update()
     } catch (error) {
       console.error(error)
     }
   }
apps/atrium-telegram/server/api/task/index.post.ts (1)

44-57: Consider making chat notification non-blocking or transactional.

If createMessage fails, the whole request fails although the task is already created. Either wrap the whole flow in a DB transaction (preferred) or make the notification a best-effort, logging failures without failing the API.

apps/atrium-telegram/server/api/task/id/[taskId]/complete.post.ts (2)

59-64: Keep the request-user context consistent after clearing focus.

You clear focusedTaskId in the DB but leave event.context.user stale. If subsequent logic reads from event.context.user, it will see the old value.

Apply this small patch:

@@
     if (user.focusedTaskId === taskId) {
       await repository.user.update(user.id, {
         focusedTaskId: null,
       })
+      // Keep in-memory context in sync for this request
+      event.context.user.focusedTaskId = null
     }

90-106: Scope and efficiency of staff notifications.

The code loads all users and filters in memory. If the user base grows, this will be slow. Prefer a repository method that queries only staff who enabled task_completed_atrium.

Example repository direction: repository.user.listStaffWithNotification('task_completed_atrium').

apps/atrium-telegram/app/pages/index.vue (1)

73-79: Make the “today only” filtering more readable and avoid repeated date/zone calls.

Current chaining with nested filters and repeated getLocalTimeZone()/isToday(parseDate(...)) is hard to read and does extra work.

Apply this refactor:

@@
-const myLists = computed(() =>
-  taskStore.lists.filter(
-    (taskList) => taskList.chat?.members.some((member) => member.userId === userStore.id),
-  ).filter((taskList) => taskStore.isTodayOnly ? taskList.tasks.filter((task) => !task.completedAt && task.date && isToday(parseDate(task.date), getLocalTimeZone())).length : true),
-)
-const myTodayTasks = computed(() => myLists.value.flatMap((taskList) => taskList.tasks.filter((task) => !task.completedAt && task.date && isToday(parseDate(task.date), getLocalTimeZone()))))
+const tz = getLocalTimeZone()
+const isPlannedForToday = (task: { completedAt?: string | null; date?: string | null }) =>
+  !task.completedAt && !!task.date && isToday(parseDate(task.date as string), tz)
+
+const myLists = computed(() => {
+  const mine = taskStore.lists.filter((taskList) =>
+    taskList.chat?.members.some((member) => member.userId === userStore.id),
+  )
+  if (!taskStore.isTodayOnly) return mine
+  return mine.filter((taskList) => taskList.tasks.some(isPlannedForToday))
+})
+
+const myTodayTasks = computed(() =>
+  myLists.value.flatMap((taskList) => taskList.tasks.filter(isPlannedForToday)),
+)
apps/atrium-telegram/server/api/task/id/[taskId]/index.patch.ts (1)

40-45: Use a 404 for non-existent task list to reflect reality.

A missing list is not a server error. Consider returning 404 instead of 500 for “Task list not found” to keep status codes semantically accurate and consistent with other 404s in this handler.

-      throw createError({
-        statusCode: 500,
-        message: 'Task list not found',
-      })
+      throw createError({
+        statusCode: 404,
+        message: 'Task list not found',
+      })
apps/atrium-telegram/app/components/TaskCard.vue (2)

182-194: Avoid shadowing toastId — rename local variables for clarity.

You have a top-level toastId ref and re-declare const toastId inside onFocus/onUnfocus. This is confusing and easy to misuse.

Apply this diff:

-async function onFocus() {
-  const toastId = actionToast.start()
+async function onFocus() {
+  const actionId = actionToast.start()
@@
-    actionToast.success(toastId, t('toast.task-focused'))
+    actionToast.success(actionId, t('toast.task-focused'))
@@
-    actionToast.error(toastId)
+    actionToast.error(actionId)
 }
 
 async function onUnfocus() {
-  const toastId = actionToast.start()
+  const actionId = actionToast.start()
@@
-    actionToast.success(toastId, t('toast.task-unfocused'))
+    actionToast.success(actionId, t('toast.task-unfocused'))
@@
-    actionToast.error(toastId)
+    actionToast.error(actionId)
 }

Also applies to: 196-209


154-155: Derive “focused” state from the current user for clarity.

Using performer.value?.focusedTaskId works only when the current user is the performer. If that’s the intended constraint, consider reading from userStore.focusedTaskId directly to make intent explicit.

-const isFocused = computed(() => task.id === performer.value?.focusedTaskId)
+const isFocused = computed(() => task.id === userStore.focusedTaskId)

If multiple users can independently “focus” tasks, keep the current logic.

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

91-93: Localize empty-state text

Hardcoded Russian string will bypass i18n. Prefer a translation key for consistency.

Example (adjust key to your messages file):

-      <p class="text-base text-dimmed">
-        Активных задач нет
-      </p>
+      <p class="text-base text-dimmed">
+        {{ $t('app.tasks.emptyActive') }}
+      </p>

21-26: Avatar alt text for accessibility

Empty alt makes these decorative. If they convey participant info, set alt to the member’s name.

Apply this diff:

-            <UAvatar
+            <UAvatar
               v-for="member in activeChatMembers"
               :key="member.id"
               :src="member?.user.avatarUrl ?? undefined"
-              alt=""
+              :alt="`${member?.user?.name ?? ''} ${member?.user?.surname ?? ''}`.trim()"
             />

102-105: Unify current user source (prop vs. store) to avoid drift

currentUserId prop is used for filtering, while userStore.id is used when creating tasks. Prefer a single source-of-truth to prevent inconsistencies.

Options:

  • Remove currentUserId prop and use userStore.id everywhere.
  • Or consistently pass and use currentUserId for all places (including createTask).

I can draft the refactor once you confirm the preferred approach.

Also applies to: 122-126, 74-75

apps/atrium-telegram/shared/services/task.ts (5)

7-11: Remove redundant | undefined in optional fields and validate date format

  • performerId includes | undefined inside a property already marked optional().
  • date should align with UI expectations (parseDate uses YYYY-MM-DD). Validate this format explicitly; allow null for “no date”.

Apply this diff:

 export const createTaskSchema = type({
   name: type('2 <= string <= 150').describe('error.length.invalid'),
   description: type('string <= 500 | undefined').describe('error.length.invalid').optional(),
-  performerId: type('string | undefined | null').describe('error.length.invalid').optional(),
-  date: type('string | undefined | null').describe('error.length.invalid').optional(),
+  performerId: type('string | null').describe('error.length.invalid').optional(),
+  // Expect ISO date (YYYY-MM-DD) to match client parseDate
+  date: type('string & /^\\d{4}-\\d{2}-\\d{2}$/ | null').describe('error.length.invalid').optional(),
   listId: type('string').describe('error.length.invalid'),
 })
 
 export const updateTaskSchema = type({
   name: type('2 <= string <= 150').describe('error.length.invalid').optional(),
   description: type('string <= 500 | undefined').describe('error.length.invalid').optional(),
-  performerId: type('string | undefined | null').describe('error.length.invalid').optional(),
-  date: type('string | undefined | null').describe('error.length.invalid').optional(),
-  listId: type('string | undefined').describe('error.length.invalid').optional(),
+  performerId: type('string | null').describe('error.length.invalid').optional(),
+  date: type('string & /^\\d{4}-\\d{2}-\\d{2}$/ | null').describe('error.length.invalid').optional(),
+  listId: type('string').describe('error.length.invalid').optional(),
 })

Note: Adjust regex if you accept empty date or different formats on the client.

Also applies to: 15-21


37-42: Make usersId optional for PATCH semantics

Requiring usersId on update forces clients to always send the entire list, which is unusual for partial updates. Consider making it optional unless your API deliberately replaces membership wholesale.

Apply this diff:

 export const updateTaskListSchema = type({
   name: type('2 <= string <= 150').describe('error.length.invalid').optional(),
   description: type('string <= 100 | undefined').describe('error.length.invalid').optional(),
-  usersId: type('string[]').describe('error.length.invalid'),
+  usersId: type('string[]').describe('error.length.invalid').optional(),
 })

If full replacement is intended, keep as-is. Otherwise, this change aligns with PATCH semantics.


30-34: Consider non-empty constraints for usersId on create

If a task list must have at least one user, assert a minimum length.

Potential change (verify arktype DSL in your version):

-  usersId: type('string[]').describe('error.length.invalid'),
+  usersId: type('1 <= string[]').describe('error.length.invalid'),

Alternatively, add a refinement or server-side check if DSL support is uncertain.


24-27: Bound the report length

Long free-text reports can bloat payloads. Add a reasonable max length (e.g., 2000 chars).

Apply this diff:

 export const completeTaskSchema = type({
   resolution: resolutionSchema.describe('error.length.invalid'),
-  report: type('string | undefined').describe('error.length.invalid').optional(),
+  report: type('string <= 2000 | undefined').describe('error.length.invalid').optional(),
 })

30-42: Optional: Rename usersIduserIds for consistency

The identifier usersId appears extensively across UI components, shared schemas, and API routes. Renaming it to userIds will improve readability and maintain a consistent pluralization pattern.

Key areas to update (non-exhaustive):

  • UI components
    • apps/web-app/app/components/form/CreateEpicCommentBeacon.vue (lines 38, 65, 80, 87)
    • apps/web-app/app/components/form/CreateTaskList.vue (lines 71, 85)
    • apps/web-app/app/components/form/CreateChat.vue (lines 67, 81)
    • apps/web-app/app/components/form/UpdateTaskList.vue (lines 88, 99, 106)
  • Shared service schemas
    • apps/web-app/shared/services/task.ts (lines 33, 40)
    • apps/web-app/shared/services/notification.ts (line 4)
    • apps/web-app/shared/services/chat.ts (line 11)
    • apps/atrium-telegram/shared/services/task.ts (lines 33, 40)
  • Server–side API handlers
    • apps/web-app/server/api/task/list/index.post.ts (lines 30–32, 52–57)
    • apps/web-app/server/api/task/list/id/[listId].patch.ts (lines 57–61, 76–78)
    • apps/web-app/server/api/chat/index.post.ts (lines 22–24, 40–45)
    • apps/web-app/server/api/epic/comment/id/[commentId]/beacon.post.ts (lines 52–54)
    • apps/atrium-telegram/server/api/task/list/index.post.ts (lines 22–24, 44–49)
    • apps/atrium-telegram/server/api/task/list/id/[listId].patch.ts (lines 57–61, 76–78)

To locate all occurrences before renaming, run:

rg -n -C1 --no-heading '\busersId\b'

Ensure you update all import paths, type definitions, JSON keys, and route validations consistently.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f1580c6 and 9a8149b.

📒 Files selected for processing (38)
  • apps/atrium-telegram/app/app.vue (2 hunks)
  • apps/atrium-telegram/app/assets/css/styles.css (3 hunks)
  • apps/atrium-telegram/app/components/CreateCard.vue (1 hunks)
  • apps/atrium-telegram/app/components/Loader.vue (1 hunks)
  • apps/atrium-telegram/app/components/TaskCard.vue (1 hunks)
  • apps/atrium-telegram/app/components/TaskList.vue (1 hunks)
  • apps/atrium-telegram/app/components/TasksTodaySwitch.vue (1 hunks)
  • apps/atrium-telegram/app/components/modal/CompleteTask.vue (1 hunks)
  • apps/atrium-telegram/app/components/modal/CreateTask.vue (1 hunks)
  • apps/atrium-telegram/app/components/modal/CreateTaskList.vue (1 hunks)
  • apps/atrium-telegram/app/components/modal/UpdateTask.vue (1 hunks)
  • apps/atrium-telegram/app/components/modal/UpdateTaskList.vue (1 hunks)
  • apps/atrium-telegram/app/components/modal/UploadUserAvatar.vue (1 hunks)
  • apps/atrium-telegram/app/pages/index.vue (1 hunks)
  • apps/atrium-telegram/app/pages/no-auth.vue (1 hunks)
  • apps/atrium-telegram/app/stores/task.ts (1 hunks)
  • apps/atrium-telegram/app/stores/user.ts (3 hunks)
  • apps/atrium-telegram/i18n/locales/ru-RU.json (1 hunks)
  • apps/atrium-telegram/server/api/auth/me.get.ts (1 hunks)
  • apps/atrium-telegram/server/api/task/id/[taskId]/complete.post.ts (1 hunks)
  • apps/atrium-telegram/server/api/task/id/[taskId]/focus.delete.ts (1 hunks)
  • apps/atrium-telegram/server/api/task/id/[taskId]/focus.post.ts (1 hunks)
  • apps/atrium-telegram/server/api/task/id/[taskId]/index.delete.ts (1 hunks)
  • apps/atrium-telegram/server/api/task/id/[taskId]/index.patch.ts (1 hunks)
  • apps/atrium-telegram/server/api/task/index.post.ts (1 hunks)
  • apps/atrium-telegram/server/api/task/list/completed.get.ts (1 hunks)
  • apps/atrium-telegram/server/api/task/list/id/[listId].delete.ts (1 hunks)
  • apps/atrium-telegram/server/api/task/list/id/[listId].patch.ts (1 hunks)
  • apps/atrium-telegram/server/api/task/list/index.get.ts (1 hunks)
  • apps/atrium-telegram/server/api/task/list/index.post.ts (1 hunks)
  • apps/atrium-telegram/server/api/user/list/index.get.ts (1 hunks)
  • apps/atrium-telegram/server/api/user/list/staff.get.ts (1 hunks)
  • apps/atrium-telegram/server/middleware/01.auth.ts (1 hunks)
  • apps/atrium-telegram/server/utils/telegram.ts (0 hunks)
  • apps/atrium-telegram/shared/services/task.ts (1 hunks)
  • apps/atrium-telegram/shared/utils/helpers.ts (1 hunks)
  • packages/database/src/tables.ts (1 hunks)
  • packages/database/src/types.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/atrium-telegram/server/utils/telegram.ts
🧰 Additional context used
🧬 Code graph analysis (10)
apps/atrium-telegram/app/stores/user.ts (1)
packages/database/src/tables.ts (1)
  • users (75-93)
apps/atrium-telegram/app/stores/task.ts (2)
packages/database/src/types.ts (5)
  • Chat (39-39)
  • ChatMember (45-45)
  • User (24-24)
  • TaskList (87-87)
  • Task (84-84)
packages/database/src/repository/task.ts (1)
  • lists (44-68)
packages/database/src/types.ts (1)
apps/web-app/shared/services/common.ts (1)
  • UserGender (7-7)
apps/atrium-telegram/server/api/task/list/index.post.ts (1)
apps/atrium-telegram/shared/services/task.ts (1)
  • createTaskListSchema (30-34)
apps/atrium-telegram/server/middleware/01.auth.ts (1)
packages/database/src/types.ts (1)
  • User (24-24)
apps/atrium-telegram/shared/utils/helpers.ts (2)
packages/database/src/types.ts (1)
  • UserGender (19-19)
apps/atrium-telegram/shared/services/task.ts (1)
  • Resolution (4-4)
apps/atrium-telegram/server/api/task/index.post.ts (2)
apps/atrium-telegram/shared/services/task.ts (1)
  • createTaskSchema (6-12)
apps/atrium-telegram/shared/utils/helpers.ts (1)
  • suffixByGender (20-28)
apps/atrium-telegram/server/api/task/id/[taskId]/complete.post.ts (4)
apps/atrium-telegram/shared/services/task.ts (1)
  • completeTaskSchema (24-27)
packages/database/src/tables.ts (1)
  • users (75-93)
apps/atrium-telegram/shared/utils/helpers.ts (2)
  • suffixByGender (20-28)
  • getLocalizedResolution (30-39)
packages/database/src/types.ts (2)
  • User (24-24)
  • Task (84-84)
apps/atrium-telegram/server/api/task/id/[taskId]/index.patch.ts (3)
apps/atrium-telegram/shared/services/task.ts (1)
  • updateTaskSchema (15-21)
packages/database/src/types.ts (2)
  • User (24-24)
  • Task (84-84)
apps/atrium-telegram/shared/utils/helpers.ts (1)
  • suffixByGender (20-28)
apps/atrium-telegram/server/api/task/list/id/[listId].patch.ts (1)
apps/atrium-telegram/shared/services/task.ts (1)
  • updateTaskListSchema (37-41)
🪛 ESLint
apps/atrium-telegram/app/pages/index.vue

[error] 50-50: Parsing error: Unexpected token as.

(vue/no-parsing-error)

⏰ 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 (27)
apps/atrium-telegram/server/api/user/list/index.get.ts (2)

3-10: Auth guard is correct and aligned with the new middleware flow.

The 401 on missing user context is appropriate and consistent with the pattern used across the PR.


3-13: Review Required: Enforce Pagination, Field Projection, and Tenant Scoping on repository.user.list()

It looks like every direct call to repository.user.list() across the codebase (in apps/web-app/..., apps/atrium-telegram/..., task completion handlers, etc.) will return all user records with every column — including emails, tokens, Telegram IDs, and any other PII — and without any paging. Before merging, please:

• Audit the signature of repository.user.list() (in packages/database/src/repository/user.ts) to confirm whether it accepts an options object for pagination, filtering, and field selection. If it doesn’t, you’ll need to extend it (or introduce a new method) to support:

  • page/limit offsets
  • explicit select: [...] for safe fields only
  • optional where filters (e.g. tenant/workspace scoping from event.context.user.tenantId)
    • Update every usage site to pass through these parameters. For example:
 export default defineEventHandler(async (event) => {
   const user = event.context.user
   if (!user) throw createError({ statusCode: 401, message: 'Not logged in' })

-  return repository.user.list()
+  // extract pagination & search from query
+  const { page = '1', limit = '20', q } = getQuery(event)
+  const pageNum = Math.max(1, Number(page) || 1)
+  const limitNum = Math.min(100, Math.max(1, Number(limit) || 20))
+
+  return repository.user.list({
+    page:  pageNum,
+    limit: limitNum,
+    search: typeof q === 'string' ? q : undefined,
+    // If multi-tenant: uncomment below
+    // where: { tenantId: user.tenantId },
+    select: ['id','username','firstName','lastName','avatarUrl','role']
+  })
})

• Don’t forget to apply the same pattern in:

  • apps/atrium-telegram/server/api/user/list/index.get.ts
  • all task/epic notification handlers that call repository.user.list()
  • the staff‐listing endpoint (repository.user.findStaff()) if it should also be paginated/projection‐limited.

This will prevent unbounded data loads, reduce PII exposure, and lay the groundwork for tenant isolation.

apps/atrium-telegram/server/api/user/list/staff.get.ts (1)

6-9: Ensure Explicit Imports or Confirm Auto-Imports for H3 Helpers

It looks like this handler is relying on Nitro’s H3 helpers (createError, getQuery, etc.) without an explicit import. Unless your project’s nuxt.config.ts (or equivalent) has enabled H3 auto-imports, this will break at runtime. Please choose one:

• If you have H3 auto-imports enabled for this app (e.g. via imports.presets: ['h3'] in your Nuxt/Nitro config), add a comment referencing that configuration here.
• Otherwise, add the necessary imports at the top of apps/atrium-telegram/server/api/user/list/staff.get.ts:

+ import { createError, getQuery } from 'h3'

—particularly if you end up using getQuery elsewhere in this handler.

Please update accordingly so it can’t slip past CI.

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

2-11: No CSS config found; please confirm token definitions

I didn’t locate any Tailwind or UnoCSS config files (e.g. tailwind.config., unocss.config.) in the repo, so it’s unclear whether these custom utilities are defined. Please verify that the following tokens exist in your design system or CSS framework:

  • border-default
  • text-muted/50
  • min-h-40

If they’re not defined, you’ll see missing or incorrect styling. Consider replacing them with existing utilities—for example:

  • border-gray-200 instead of border-default
  • text-gray-500/50 instead of text-muted/50
  • min-h-[10rem] instead of min-h-40
packages/database/src/types.ts (1)

10-20: Centralizing UserType/UserGender looks good

The string-literal unions for UserType and UserGender align with table defaults and keep the model tight. No issues here.

packages/database/src/tables.ts (1)

1-1: Approve: Type-only imports confirmed

Verified that both sides use only type-only imports, preventing any runtime circular dependencies:

  • packages/database/src/tables.ts (line 1): import type { NotificationOption, UserGender, UserType } from './types'
  • packages/database/src/types.ts (line 2): import type * as tables from './tables'

No non-type imports detected.

apps/atrium-telegram/i18n/locales/ru-RU.json (1)

8-20: Cross-locale parity requires manual verification

I checked apps/atrium-telegram/i18n/locales/ and only found a single locale file (ru-RU.json), so there aren’t any other JSONs to diff against automatically. Please ensure that, whenever you add or update other locales (e.g. en-US.json, de-DE.json, etc.), the following new keys from ru-RU.json are added there to avoid missing translations at runtime:

  • File: apps/atrium-telegram/i18n/locales/ru-RU.json
    • home
    • create.task-list.button
    • create.task-list.title
    • update.user-photo.button
    • update.user-photo.title
apps/atrium-telegram/server/api/auth/me.get.ts (1)

5-11: Context-based auth handoff looks good.

Reading the authenticated user from event.context.user aligns with the new middleware and simplifies the handler.

apps/atrium-telegram/server/api/task/id/[taskId]/focus.delete.ts (2)

5-11: Good request validation.

400 on missing taskId is appropriate and explicit.


13-19: Auth guard is clear and consistent.

401 “Not logged in” matches the new auth flow.

apps/atrium-telegram/server/middleware/01.auth.ts (1)

19-23: Nice: case-insensitive Authorization retrieval with graceful null.

Good defensive read of Authorization/authorization headers.

apps/atrium-telegram/app/assets/css/styles.css (1)

103-115: Confirm intent: scrolling behavior and scrollbar hiding now disabled.

Commenting out the block changes global scroll behavior and will show native scrollbars. If this is intentional for better UX, all good. If not, consider keeping scrollbar hiding while allowing body scroll.

Minimal alternative:

-/* html, body, #app {
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-  overscroll-behavior: none;
-}
-#app {
-  overflow-y: auto;
-}
-#app::-webkit-scrollbar {
-  display: none;
-  width: 0;
-} */
+#app::-webkit-scrollbar {
+  display: none;
+  width: 0;
+}
apps/atrium-telegram/server/api/task/list/index.get.ts (2)

5-11: Auth guard is in place and clear.

Early 401 via createError keeps the handler simple and avoids unnecessary DB calls.


13-16: The database “repository” export isn’t actually in src/index.ts—it’s coming from a sibling file, so let’s verify exactly where that lives before concluding how lists() is scoped. I’ve asked for its precise path; once we confirm the implementation location and signature, we can determine:

  • Whether lists() takes any user/context argument (and thus must be passed user.id).
  • Whether its return is indeed a promise (so it must be awaited to normalize errors).
  • That errorResolver is properly imported (or available globally) in this handler.

I’ll follow up as soon as we locate and inspect the repository implementation.

apps/atrium-telegram/server/api/task/list/index.post.ts (2)

69-72: Confirm errorResolver availability.

errorResolver is used but not imported. If it’s injected globally, ignore. Otherwise, add the import to avoid TS errors.

Do you have a global declaration for errorResolver? If not, I can add the correct import for this module.


8-11: No changes needed for ArkType validation usage.

ArkType’s default validator functions return either the parsed data or an error object (an instance of type.errors), rather than throwing on invalid input. Checking instanceof type.errors is the recommended pattern to detect validation failures and then throw or handle them as needed (arktype.io). You can safely ignore the original concern.

Likely an incorrect or invalid review comment.

apps/atrium-telegram/server/api/task/list/id/[listId].patch.ts (4)

31-38: Authorization check is solid.

Validating edit permissions against chat membership before updating is appropriate.


41-44: Verify ArkType usage on update schema as well.

Same concern as the POST handler: confirm that updateTaskListSchema(body) + instanceof type.errors is the correct pattern; otherwise validation may not work as expected.

If update semantics should be truly partial, consider making usersId optional in the schema to allow metadata-only updates.


82-85: Return shape LGTM.

Returning { ok: true, result: updatedList } is consistent with nearby endpoints.


86-89: Confirm errorResolver import/availability.

As with the POST route, ensure errorResolver is globally provided or imported to avoid TS errors.

apps/atrium-telegram/server/api/task/id/[taskId]/focus.post.ts (1)

53-56: Confirm errorResolver availability.

Same note as other handlers—import or ensure it’s globally available.

apps/atrium-telegram/app/stores/task.ts (2)

13-18: Store structure and types look good.

Nice, focused Pinia store with explicit TaskList/Chat shapes that match repository outputs.


69-75: Nice UX reset.

Resetting filters on route change is a good touch.

apps/atrium-telegram/server/api/task/id/[taskId]/index.patch.ts (2)

47-55: Confirm domain assumption: lists without a chat are always non-editable.

canEdit is based solely on list.chat?.members. If a list can exist without a chat, this condition becomes false and the API responds 403, effectively making such tasks uneditable. If that’s not intended, consider another access predicate.

Would you like me to propose an alternative guard that also allows list owners or performers?


1-6: The file apps/atrium-telegram/server/api/task/id/[taskId]/index.patch.ts is missing the import for errorResolver, and the catch blocks at the bottom reference errorResolver without defining it. To align with the existing pattern across other endpoints, we should:

• Add the import at the top alongside the other shared utils.
• Update lines 86–89 (the catch block) to call throw errorResolver(error) (if not already doing so) or to wrap the error appropriately.

Proposed diff:

--- a/apps/atrium-telegram/server/api/task/id/[taskId]/index.patch.ts
+++ b/apps/atrium-telegram/server/api/task/id/[taskId]/index.patch.ts
@@ 1,6c1,7
-import type { Task, User } from '@roll-stack/database'
-import { updateTaskSchema } from '#shared/services/task'
-import { suffixByGender } from '#shared/utils/helpers'
-import { repository } from '@roll-stack/database'
-import { type } from 'arktype'
+import type { Task, User } from '@roll-stack/database'
+import { updateTaskSchema } from '#shared/services/task'
+import { suffixByGender } from '#shared/utils/helpers'
+import { repository } from '@roll-stack/database'
+import { type } from 'arktype'
+// Align with other handlers by importing our shared error resolver
+import { errorResolver } from '#shared/utils/error-resolver'

@@ 86,89c87,90
-  } catch (error) {
-    // errorResolver is undefined here
-    throw errorResolver(error)
-  }
+  } catch (error) {
+    // Delegate to shared error handler for consistent API errors
+    throw errorResolver(error)
+  }

This ensures that any exceptions are handled via our centralized logic, matching patterns in other services (e.g., web-storefront, web-app, geo-vault).

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

56-77: Nice UX: gated edit/create actions behind membership

Conditional rendering via canEdit keeps actions scoped to chat members. Clean use of overlay modals and concise button UI.

apps/atrium-telegram/shared/services/task.ts (1)

3-5: Good: centralized Resolution type with runtime schema

Clean union with type inference eases reuse across server routes and UI.

Comment on lines +4 to +8
<FormCompleteTask
:task-id="taskId ?? ''"
@submitted="overlay.closeAll"
@success="overlay.closeAll"
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid passing empty string as a task id.

Forwarding '' can be treated as a real value and cause validation/API errors. If taskId is optional, let it be undefined so the child can decide.

-      <FormCompleteTask
-        :task-id="taskId ?? ''"
+      <FormCompleteTask
+        :task-id="taskId"
         @submitted="overlay.closeAll"
         @success="overlay.closeAll"
       />
📝 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
<FormCompleteTask
:task-id="taskId ?? ''"
@submitted="overlay.closeAll"
@success="overlay.closeAll"
/>
<FormCompleteTask
:task-id="taskId"
@submitted="overlay.closeAll"
@success="overlay.closeAll"
/>
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/modal/CompleteTask.vue around lines 4–8,
the template currently forces an empty string for taskId (:task-id="taskId ??
''"), which can be misinterpreted as a real ID; remove the fallback and pass the
prop as-is (or omit the prop when undefined) so the child receives undefined
when taskId is absent and can handle validation/API logic appropriately (e.g.,
change to :task-id="taskId" or conditionally render the prop only when taskId is
defined).

Comment on lines +4 to +9
<FormUpdateTask
:task-id="taskId ?? ''"
@submitted="overlay.closeAll"
@success="overlay.closeAll"
/>
</template>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Close the modal only on success and make taskId required

Closing on submitted can hide errors if the update fails. Also, the component renders with taskId ?? '', which risks invoking the form with an empty ID. Make taskId required (or guard render) and close only on success. Explicitly calling the method avoids any handler binding surprises.

   <UModal :title="$t('app.update.task.title')">
     <template #body>
       <FormUpdateTask
-        :task-id="taskId ?? ''"
-        @submitted="overlay.closeAll"
-        @success="overlay.closeAll"
+        :task-id="taskId"
+        @success="() => overlay.closeAll()"
       />
     </template>
   </UModal>
@@
-defineProps<{
-  taskId?: string
-}>()
+defineProps<{
+  taskId: string
+}>()

If taskId can be legitimately absent, instead keep it optional but use a guard:

<FormUpdateTask v-if="taskId" :task-id="taskId" @success="() => overlay.closeAll()" />

Please confirm what FormUpdateTask emits on failure/success. If it already emits only success, removing submitted is safe.

Also applies to: 13-19


I’ll locate and inspect the FormUpdateTask.vue component to confirm which events it emits. This will tell us whether it emits both submitted and success, or only success.


🏁 Script executed:

#!/usr/bin/env bash
set -e
# 1. Find the component file
file=$(find . -type f -name "FormUpdateTask.vue" | head -n1)
if [ -z "$file" ]; then
  echo "FormUpdateTask.vue not found in the repo."
  exit 0
fi
echo "Found FormUpdateTask.vue at: $file"

# 2. Show relevant sections: defineEmits or emits option
echo
echo "----- defineEmits/emits declaration -----"
rg -n "defineEmits" "$file" || rg -n "emits" "$file" || echo "No defineEmits/emits declaration found."

# 3. Show places where events are emitted
echo
echo "----- $emit calls for success/submitted -----"
rg -n "\$emit" "$file" | grep -E "success|submitted" || echo "No $emit calls for 'success' or 'submitted'."

Length of output: 189


I’ll locate the import of FormUpdateTask in UpdateTask.vue, then find and inspect its definition to see which events it emits.


🏁 Script executed:

#!/usr/bin/env bash
set -e
# 1. Locate UpdateTask.vue
update_file=$(find . -type f -path "*apps/atrium-telegram/app/components/modal/UpdateTask.vue" | head -n1)
echo "UpdateTask.vue at: $update_file"

# 2. Show import of FormUpdateTask
echo
echo "----- Import Statement -----"
rg -n "import.*FormUpdateTask" "$update_file"

# 3. Show beginning of UpdateTask.vue for context
echo
echo "----- First 50 lines of UpdateTask.vue -----"
sed -n '1,50p' "$update_file"

# 4. List all .vue files referencing FormUpdateTask to locate its file
echo
echo "----- Files referencing FormUpdateTask -----"
rg -l "FormUpdateTask" -g "*.vue"

Length of output: 509


I’ll search the entire repo for FormUpdateTask references to locate its file and then inspect its emits:


🏁 Script executed:

#!/usr/bin/env bash
set -e
echo "Searching for FormUpdateTask usage..."
rg -n "FormUpdateTask" -C3 .

# If a path is found, inspect its defineEmits/emits
file=$(rg -l "FormUpdateTask" -g "*.vue" | head -n1)
if [ -n "$file" ]; then
  echo
  echo "Inspecting emits in $file:"
  rg -n "defineEmits" "$file" || rg -n "emits" "$file" || echo "No defineEmits/emits declaration found."
  echo
  echo "Looking for $emit calls in $file:"
  rg -n "\$emit" "$file" | grep -E "success|submitted" || echo "No success/submitted emits."
else
  echo
  echo "Component file not located via *.vue search; may be auto-registered or named differently."
fi

Length of output: 3605


I’ll search for non-modal UpdateTask.vue files (likely the actual form component) and inspect their emitted events:


🏁 Script executed:

#!/usr/bin/env bash
set -e

echo "Listing all UpdateTask.vue files outside modal directories:"
find . -type f -iname "*update*task*.vue" | grep -v "/modal/" || echo "No non-modal UpdateTask.vue files found"

file=$(find . -type f -iname "*update*task*.vue" | grep -v "/modal/" | head -n1)
if [ -n "$file" ]; then
  echo ""
  echo "Inspecting emits in $file"
  rg -n "defineEmits" "$file" || rg -n "emits" "$file" || echo "No defineEmits/emits declaration found in $file"
  echo ""
  echo "Looking for \$emit calls in $file"
  rg -n "\$emit" "$file" | grep -E "success|submitted" || echo "No success/submitted emits found in $file"
else
  echo ""
  echo "Component file not located via pattern search."
fi

Length of output: 1235


I’ll locate and inspect the form components under the Atrium Telegram app to confirm which events they emit:


🏁 Script executed:

#!/usr/bin/env bash
set -e

echo "Listing all .vue form components under apps/atrium-telegram/app/components/form..."
files=$(find apps/atrium-telegram/app/components/form -type f -name "*.vue")
echo "$files"

for file in $files; do
  echo
  echo "----- Inspecting $file -----"
  # Check for defineEmits or emits declaration
  rg -n "defineEmits" "$file" || rg -n "emits" "$file" || echo "No defineEmits/emits declaration found."

  echo
  echo "----- Searching for emit('success' or emit('submitted') calls -----"
  rg -n "emit\\('success'" "$file" || rg -n "emit\\('submitted'" "$file" || echo "No success/submitted emit calls."
done

Length of output: 339


I’ll search across all Vue components for their defineEmits declarations and look for any emit('success') or emit('submitted') calls to confirm what events FormUpdateTask actually fires:


🏁 Script executed:

#!/usr/bin/env bash
set -e

echo "🔍 Scanning all .vue files for defineEmits..."
rg -n "defineEmits" -g "*.vue" || echo "[none]"

echo
echo "🔍 Looking for emit('success') usage..."
rg -n "emit\(.*['\"]success['\"]" -g "*.vue" || echo "[none]"

echo
echo "🔍 Looking for emit('submitted') usage..."
rg -n "emit\(.*['\"]submitted['\"]" -g "*.vue" || echo "[none]"

Length of output: 11038


Ensure taskId is required and close modal only on success

Verified that FormUpdateTask declares both submitted and success in its defineEmits(['success','submitted']), and that it emits submitted on every attempt (including failures). Closing on submitted will prematurely hide errors. We should:

  • Remove the @submitted listener.
  • Enforce a valid taskId by making the prop required (or guarding the render).
  • Explicitly call overlay.closeAll() only in the @success handler.

Locations to update:

  • apps/atrium-telegram/app/components/modal/UpdateTask.vue (lines 4–9)
  • apps/atrium-telegram/app/components/modal/UpdateTaskList.vue (lines 13–19)

Suggested diff for UpdateTask.vue:

   <UModal :title="$t('app.update.task.title')">
     <template #body>
       <FormUpdateTask
-        :task-id="taskId ?? ''"
-        @submitted="overlay.closeAll"
-        @success="overlay.closeAll"
+        :task-id="taskId"
+        @success="() => overlay.closeAll()"
       />
     </template>
   </UModal>
@@
-defineProps<{
-  taskId?: string
-}>()
+defineProps<{
+  taskId: string
+}>()

If taskId truly can be absent, guard the render instead:

<FormUpdateTask
  v-if="taskId"
  :task-id="taskId"
  @success="() => overlay.closeAll()"
/>

Apply the same changes in UpdateTaskList.vue (lines 13–19).

🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/modal/UpdateTask.vue (lines 4–9) and
apps/atrium-telegram/app/components/modal/UpdateTaskList.vue (lines 13–19),
remove the @submitted listener so the modal is not closed on every submit
attempt; ensure taskId is required or guard the render (e.g., render
FormUpdateTask only when taskId is truthy) and change the success handler to
explicitly call overlay.closeAll() (e.g., @success="() => overlay.closeAll()")
so the modal closes only on successful updates.

<UModal :title="$t('app.update.task-list.title')">
<template #body>
<FormUpdateTaskList
:list-id="listId ?? ''"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Passing empty string as list-id can break consumers; make listId required

Forwarding listId ?? '' will pass an empty id to FormUpdateTaskList, likely causing failed fetches or server 400s. Make listId required and pass it directly; conditionally render the form only when it exists if this modal can open before data is ready.

Apply this diff:

-        :list-id="listId ?? ''"
+        :list-id="listId"

Optionally guard rendering:

-      <FormUpdateTaskList
+      <FormUpdateTaskList
         :list-id="listId"

…and add v-if="listId" on the Form if you cannot make the prop required.

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

🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/modal/UpdateTaskList.vue around line 5,
the template forwards listId using "listId ?? ''" which can send an empty string
to FormUpdateTaskList and break consumers; make listId a required prop on
UpdateTaskList and pass it directly as :list-id="listId". If you cannot make the
prop required, conditionally render the FormUpdateTaskList only when listId is
present by adding v-if="listId" to the form invocation instead of defaulting to
an empty string.

Comment on lines +14 to +16
defineProps<{
listId?: string
}>()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Strengthen prop contract: listId should be required

If this modal is for updating an existing list, listId should be mandatory.

-defineProps<{
-  listId?: string
-}>()
+defineProps<{
+  listId: string
+}>()
📝 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
defineProps<{
listId?: string
}>()
defineProps<{
listId: string
}>()
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/modal/UpdateTaskList.vue around lines 14
to 16, the defineProps currently declares listId as optional; change the prop to
be required by updating the TypeScript signature to declare listId as a
non-optional string (remove the ?), and adjust any code paths that assumed
undefined (or add explicit runtime assertions) so the component always receives
a valid listId; also update any parent component usages to ensure they pass
listId when rendering this modal.

Comment on lines +159 to +179
const items = computed<DropdownMenuItem[]>(() => {
const menuItems: DropdownMenuItem[] = [
{
label: isFocused.value ? 'Убрать фокус' : 'Сфокусироваться',
icon: 'i-lucide-goal',
color: 'neutral',
disabled: false,
onSelect: isFocused.value ? onUnfocus : onFocus,
condition: canFocus.value,
},
{
label: 'Редактировать',
icon: 'i-lucide-edit',
disabled: isCompleted.value,
onSelect: () => modalUpdateTask.open({ taskId: task.id }),
condition: canEdit.value,
},
]

return menuItems.filter((item) => item.condition)
})
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

Remove non-existent condition property from DropdownMenuItem and build the list conditionally.

DropdownMenuItem likely doesn’t define condition, and you typed menuItems as DropdownMenuItem[]. This will cause a TS error and you’re filtering on a property that shouldn’t exist.

Apply this diff:

-const items = computed<DropdownMenuItem[]>(() => {
-  const menuItems: DropdownMenuItem[] = [
-    {
-      label: isFocused.value ? 'Убрать фокус' : 'Сфокусироваться',
-      icon: 'i-lucide-goal',
-      color: 'neutral',
-      disabled: false,
-      onSelect: isFocused.value ? onUnfocus : onFocus,
-      condition: canFocus.value,
-    },
-    {
-      label: 'Редактировать',
-      icon: 'i-lucide-edit',
-      disabled: isCompleted.value,
-      onSelect: () => modalUpdateTask.open({ taskId: task.id }),
-      condition: canEdit.value,
-    },
-  ]
-
-  return menuItems.filter((item) => item.condition)
-})
+const items = computed<DropdownMenuItem[]>(() => {
+  const menuItems: DropdownMenuItem[] = []
+  if (canFocus.value) {
+    menuItems.push({
+      label: isFocused.value ? 'Убрать фокус' : 'Сфокусироваться',
+      icon: 'i-lucide-goal',
+      color: 'neutral',
+      disabled: false,
+      onSelect: isFocused.value ? onUnfocus : onFocus,
+    })
+  }
+  if (canEdit.value) {
+    menuItems.push({
+      label: 'Редактировать',
+      icon: 'i-lucide-edit',
+      disabled: isCompleted.value,
+      onSelect: () => modalUpdateTask.open({ taskId: task.id }),
+    })
+  }
+  return menuItems
+})
📝 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
const items = computed<DropdownMenuItem[]>(() => {
const menuItems: DropdownMenuItem[] = [
{
label: isFocused.value ? 'Убрать фокус' : 'Сфокусироваться',
icon: 'i-lucide-goal',
color: 'neutral',
disabled: false,
onSelect: isFocused.value ? onUnfocus : onFocus,
condition: canFocus.value,
},
{
label: 'Редактировать',
icon: 'i-lucide-edit',
disabled: isCompleted.value,
onSelect: () => modalUpdateTask.open({ taskId: task.id }),
condition: canEdit.value,
},
]
return menuItems.filter((item) => item.condition)
})
const items = computed<DropdownMenuItem[]>(() => {
const menuItems: DropdownMenuItem[] = []
if (canFocus.value) {
menuItems.push({
label: isFocused.value ? 'Убрать фокус' : 'Сфокусироваться',
icon: 'i-lucide-goal',
color: 'neutral',
disabled: false,
onSelect: isFocused.value ? onUnfocus : onFocus,
})
}
if (canEdit.value) {
menuItems.push({
label: 'Редактировать',
icon: 'i-lucide-edit',
disabled: isCompleted.value,
onSelect: () => modalUpdateTask.open({ taskId: task.id }),
})
}
return menuItems
})
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/TaskCard.vue around lines 159-179, the
code adds a non-existent "condition" property to objects typed as
DropdownMenuItem and then filters by it causing a TypeScript error; instead,
construct the menuItems array conditionally (only push each DropdownMenuItem
when its corresponding canX.value is true) and remove the "condition" property
and the final filter call so the array contains only valid DropdownMenuItem
objects.

Comment on lines +41 to +52
// Add all bots as members too
const bots = await repository.user.findBots()
const botIds = bots.map((bot) => bot.id)
data.usersId.push(...botIds)

// Create members
for (const userId of data.usersId) {
await repository.chat.createMember({
chatId: chat.id,
userId,
})
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Ensure unique membership, include the current user, and avoid duplicates.

Pushing bot IDs into data.usersId may create duplicates and still omits the current user unless explicitly provided. Build a unique set once and iterate over it.

Apply this diff:

-    // Add all bots as members too
-    const bots = await repository.user.findBots()
-    const botIds = bots.map((bot) => bot.id)
-    data.usersId.push(...botIds)
-
-    // Create members
-    for (const userId of data.usersId) {
-      await repository.chat.createMember({
-        chatId: chat.id,
-        userId,
-      })
-    }
+    // Add all bots and ensure the current user is a member; dedupe
+    const bots = await repository.user.findBots()
+    const botIds = bots.map((b) => b.id)
+    const memberIds = Array.from(new Set([user.id, ...data.usersId, ...botIds]))
+
+    // Create members
+    for (const userId of memberIds) {
+      await repository.chat.createMember({
+        chatId: chat.id,
+        userId,
+      })
+    }

If idempotency is guaranteed on createMember, consider Promise.all for latency; otherwise keep sequential to preserve clearer failure semantics.

📝 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
// Add all bots as members too
const bots = await repository.user.findBots()
const botIds = bots.map((bot) => bot.id)
data.usersId.push(...botIds)
// Create members
for (const userId of data.usersId) {
await repository.chat.createMember({
chatId: chat.id,
userId,
})
}
// Add all bots and ensure the current user is a member; dedupe
const bots = await repository.user.findBots()
const botIds = bots.map((b) => b.id)
const memberIds = Array.from(new Set([user.id, ...data.usersId, ...botIds]))
// Create members
for (const userId of memberIds) {
await repository.chat.createMember({
chatId: chat.id,
userId,
})
}
🤖 Prompt for AI Agents
In apps/atrium-telegram/server/api/task/list/index.post.ts around lines 41-52,
pushing bot IDs onto data.usersId can create duplicates and may omit the current
user; instead construct a Set containing data.usersId, the botIds (from
repository.user.findBots()), and the current user's id to ensure uniqueness and
inclusion, then iterate over that Set to call repository.chat.createMember for
each id; if createMember is idempotent you may run them in parallel with
Promise.all, otherwise keep sequential awaits to preserve failure semantics.

Comment on lines +6 to +9
throw createError({
statusCode: 401,
message: 'Not logged in',
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Confirm auto-import of createError (or import explicitly).

If your Nuxt/Nitro config doesn’t auto-import h3 utilities on the server, add an explicit import to avoid runtime ReferenceErrors.

If needed:

-import { repository } from '@roll-stack/database'
+import { repository } from '@roll-stack/database'
+import { createError, getQuery } from 'h3'

🏁 Script executed:

#!/bin/bash
# Check if createError is generally used with/without imports in the repo.
rg -nP "(^|[^.])\bcreateError\s*\(" -C2
rg -nP "from\s+['\"]h3['\"]\s*;?" -C0

Length of output: 124136


Ensure createError Is Imported from h3

It looks like apps/atrium-telegram/server/api/user/list/index.get.ts throws createError(...) without an explicit import. Unless your Nuxt/Nitro config is set up to auto-import H3 utilities, this will cause a runtime ReferenceError.

• File needing update:

  • apps/atrium-telegram/server/api/user/list/index.get.ts

Apply this diff to the top of the file:

 import { repository } from '@roll-stack/database'
+import { createError } from 'h3'
 
 export default defineEventHandler(async (event) => {
   const user = event.context.user
   if (!user) {
     throw createError({
       statusCode: 401,
       message: 'Not logged in',
     })
   }
   // …
 })

Please add the import (or confirm that your config auto-imports H3 utilities) to prevent potential ReferenceErrors at runtime.

📝 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
throw createError({
statusCode: 401,
message: 'Not logged in',
})
// apps/atrium-telegram/server/api/user/list/index.get.ts
import { repository } from '@roll-stack/database'
import { createError } from 'h3'
export default defineEventHandler(async (event) => {
const user = event.context.user
if (!user) {
throw createError({
statusCode: 401,
message: 'Not logged in',
})
}
// …
})
🤖 Prompt for AI Agents
In apps/atrium-telegram/server/api/user/list/index.get.ts around lines 6 to 9,
the code calls createError(...) but does not import it; add an explicit import
for createError from 'h3' at the top of the file (or confirm Nuxt/Nitro
auto-imports H3 utilities) so createError is defined and avoids a runtime
ReferenceError.

})
}

return repository.user.findStaff()
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

Enforce authorization: only staff/admin should query the staff list.

Currently any authenticated user can access staff records. Add a role/permission check and return 403 when the user lacks privileges.

Proposed fix (adapt field names to your user shape: role/roles/isStaff/isAdmin):

 export default defineEventHandler(async (event) => {
   const user = event.context.user
   if (!user) {
     throw createError({
       statusCode: 401,
       message: 'Not logged in',
     })
   }
 
+  // Authorization: allow only staff/admin
+  const isStaff = Boolean((user as any)?.isStaff || (user as any)?.isAdmin || (user as any)?.roles?.includes('staff') || (user as any)?.roles?.includes('admin'))
+  if (!isStaff) {
+    throw createError({ statusCode: 403, statusMessage: 'Forbidden' })
+  }
+
-  return repository.user.findStaff()
+  return repository.user.findStaff()
 })
📝 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
return repository.user.findStaff()
export default defineEventHandler(async (event) => {
const user = event.context.user
if (!user) {
throw createError({
statusCode: 401,
message: 'Not logged in',
})
}
// Authorization: allow only staff/admin
const isStaff = Boolean(
(user as any)?.isStaff ||
(user as any)?.isAdmin ||
(user as any)?.roles?.includes('staff') ||
(user as any)?.roles?.includes('admin')
)
if (!isStaff) {
throw createError({
statusCode: 403,
statusMessage: 'Forbidden',
})
}
return repository.user.findStaff()
})

Comment on lines +17 to +18
const botToken = process.env.NODE_ENV !== 'development' ? telegram.atriumBotToken : telegram.devBotToken

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

Dev env bug: using atriumBotId with devBotToken will fail lookups.

When NODE_ENV === 'development' you pick devBotToken, but still query with telegram.atriumBotId. Use the matching botId per environment.

Apply:

-    const botToken = process.env.NODE_ENV !== 'development' ? telegram.atriumBotToken : telegram.devBotToken
+    const isDev = process.env.NODE_ENV === 'development'
+    const botToken = isDev ? telegram.devBotToken : telegram.atriumBotToken
+    const botId = isDev ? telegram.devBotId : telegram.atriumBotId
@@
-    const userInDB = await repository.telegram.findUserByTelegramIdAndBotId(telegramData.user.id.toString(), telegram.atriumBotId)
+    const userInDB = await repository.telegram.findUserByTelegramIdAndBotId(
+      telegramData.user.id.toString(),
+      botId
+    )

Also applies to: 34-35

🤖 Prompt for AI Agents
In apps/atrium-telegram/server/middleware/01.auth.ts around lines 17-18 (and
also at lines 34-35) you select devBotToken when NODE_ENV === 'development' but
still use telegram.atriumBotId for lookups, causing lookup failures; update the
logic to use the matching botId for the selected token (pick telegram.devBotId
when using devBotToken and telegram.atriumBotId when using atriumBotToken) and
replace the hardcoded id usage at both places so the botId is determined from
the same environment-aware conditional as the token.

Comment on lines +47 to +50
function validateTelegramData(authData: string, botToken: string): InitData | undefined {
validate(authData, botToken, { expiresIn: 0 })
return parse(authData)
}
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

Security: expiresIn: 0 disables freshness checks.

validate(..., { expiresIn: 0 }) effectively accepts arbitrarily old init data. Use a sane TTL (e.g., 3600s) and make it configurable via runtime config.

Apply:

-function validateTelegramData(authData: string, botToken: string): InitData | undefined {
-  validate(authData, botToken, { expiresIn: 0 })
-  return parse(authData)
-}
+function validateTelegramData(
+  authData: string,
+  botToken: string,
+  ttlSeconds = useRuntimeConfig().telegram?.initDataTTL ?? 3600
+): InitData | undefined {
+  validate(authData, botToken, { expiresIn: ttlSeconds })
+  return parse(authData)
+}

And pass ttl at call site:

-    const telegramData = validateTelegramData(authData, botToken)
+    const telegramData = validateTelegramData(authData, botToken)

If you adopt the parameterized signature, update the call to explicitly pass TTL from config.

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

🤖 Prompt for AI Agents
In apps/atrium-telegram/server/middleware/01.auth.ts around lines 47 to 50,
validate(...) is called with { expiresIn: 0 } which disables freshness checks;
change the function to accept a TTL parameter (default to a sane value like 3600
seconds) and use that instead of 0, make the TTL configurable via runtime config
(env or config object), and update the call site(s) to pass the configured TTL
explicitly so validate(authData, botToken, { expiresIn: ttl }) is used.

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

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

See analysis details on SonarQube Cloud

@hmbanan666 hmbanan666 merged commit bc89579 into main Aug 26, 2025
6 of 8 checks passed
@hmbanan666 hmbanan666 deleted the components branch August 26, 2025 15:16
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