Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/atrium-telegram/app/components/form/CreateFlowItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
</template>

<script setup lang="ts">
import type { CreateFlowItem } from '#shared/services/flow'
import type { FormSubmitEvent } from '@nuxt/ui'
import { createFlowItemSchema } from '#shared/services/flow'
import type { CreateFlowItem } from '@roll-stack/schema'
import { createFlowItemSchema } from '@roll-stack/schema'

const emit = defineEmits(['success', 'submitted'])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
</template>

<script setup lang="ts">
import type { CreateFlowItemComment } from '#shared/services/flow'
import type { FormSubmitEvent } from '@nuxt/ui'
import { createFlowItemCommentSchema } from '#shared/services/flow'
import type { CreateFlowItemComment } from '@roll-stack/schema'
import { createFlowItemCommentSchema } from '@roll-stack/schema'

const { itemId } = defineProps<{ itemId: string }>()

Expand Down
1 change: 1 addition & 0 deletions apps/atrium-telegram/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@pinia/nuxt": "catalog:",
"@roll-stack/database": "workspace:*",
"@roll-stack/essence": "workspace:*",
"@roll-stack/schema": "workspace:*",
"@roll-stack/ui": "workspace:*",
"@telegram-apps/init-data-node": "catalog:",
"@telegram-apps/sdk-vue": "catalog:",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createFlowItemCommentSchema } from '#shared/services/flow'
import { db } from '@roll-stack/database'
import { createFlowItemCommentSchema } from '@roll-stack/schema'
import { type } from 'arktype'

export default defineEventHandler(async (event) => {
Expand Down
2 changes: 1 addition & 1 deletion apps/atrium-telegram/server/api/flow/index.post.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createFlowItemSchema } from '#shared/services/flow'
import { db } from '@roll-stack/database'
import { createFlowItemSchema } from '@roll-stack/schema'
import { type } from 'arktype'

export default defineEventHandler(async (event) => {
Expand Down
6 changes: 3 additions & 3 deletions apps/web-app/app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const notification = useNotificationStore()
const post = usePostStore()
const print = usePrintStore()
const activity = useActivityStore()
const epic = useEpicStore()
const flow = useFlowStore()
const locker = useLockerStore()

await Promise.all([
Expand All @@ -77,7 +77,7 @@ onMounted(async () => {
task.update(),
ticket.update(),
notification.update(),
epic.update(),
flow.update(),
])

interval = setInterval(async () => {
Expand All @@ -87,7 +87,7 @@ onMounted(async () => {
task.update(),
ticket.update(),
notification.update(),
epic.update(),
flow.update(),
])
}, 30000)
})
Expand Down
93 changes: 93 additions & 0 deletions apps/web-app/app/components/FlowItemCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<template>
<UCard>
<div class="flex flex-col gap-3">
<div class="flex flex-row gap-3 items-center">
<UAvatar
v-if="item.userId && item.type === 'user_post'"
:src="userAvatarUrl"
class="size-8"
/>
<UIcon
v-else
:name="getIconName(item.type)"
class="size-8 text-secondary"
/>

<div v-if="!isViewed" 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>

<div class="flex flex-col gap-2">
<p class="text-sm/4 md:text-lg/6 font-bold whitespace-pre-wrap">
{{ item.title }}
</p>

<p class="text-base/5 whitespace-pre-wrap">
{{ item.description }}
</p>
</div>

<div v-if="!isViewed">
<UButton
variant="solid"
color="secondary"
size="lg"
icon="i-lucide-check"
label="Отметить как просмотренное"
class="w-full justify-center font-bold"
@click="flowStore.addView(item.id)"
/>
</div>

<div class="flex justify-between items-center">
<div class="flex flex-row gap-4">
<div class="flex flex-row gap-1.5 items-center text-sm text-muted">
<UIcon name="i-lucide-message-circle" class="size-5" />
<p>{{ item?.comments.length }}</p>
</div>
</div>

<time
:datetime="item.createdAt"
class="text-sm text-muted"
v-text="format(new Date(item.createdAt), 'd MMMM yyyy в HH:mm', { locale: ru })"
/>
</div>
</div>
</UCard>
</template>

<script setup lang="ts">
import { format } from 'date-fns'
import { ru } from 'date-fns/locale'

const { item } = defineProps<{
item: FlowItemWithData
}>()

const userStore = useUserStore()
const flowStore = useFlowStore()
const isViewed = computed(() => item?.views.some((view) => view.userId === useUserStore().id))
const userAvatarUrl = computed(() => userStore.users.find((user) => user.id === item.userId)?.avatarUrl ?? undefined)

function getIconName(type: FlowItemWithData['type']): string {
switch (type) {
case 'user_post':
return 'i-lucide-square-user-round'
case 'partner_maintenance':
return 'i-lucide-user'
case 'daily_task_report':
case 'weekly_task_report':
return 'i-lucide-clipboard-check'
default:
return 'i-lucide-clipboard'
}
}
</script>
15 changes: 9 additions & 6 deletions apps/web-app/app/components/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<div class="flex items-center shrink-0 gap-3">
<slot />

<UTooltip text="Уведомления" :shortcuts="['N']">
<UTooltip text="Поток">
<UButton
color="neutral"
variant="ghost"
Expand All @@ -20,10 +20,14 @@
>
<UChip
color="error"
inset
:show="haveNotifications"
size="3xl"
:text="flowStore.nowViewedItemsCount.toString()"
:show="flowStore.nowViewedItemsCount > 0"
:ui="{
base: 'right-0 px-1.5 py-2 ring-2 tg-text-button font-bold motion-translate-y-loop-25 motion-duration-3500',
}"
>
<UIcon name="i-lucide-bell" class="size-5 shrink-0" />
<UIcon name="i-lucide-waves" class="size-5 shrink-0" />
</UChip>
</UButton>
</UTooltip>
Expand All @@ -44,6 +48,5 @@ defineProps<{ title: string }>()

const { isNotificationsOpened } = useApp()

const notificationStore = useNotificationStore()
const haveNotifications = computed(() => notificationStore.notifications.filter((notification) => !notification.viewedAt).length > 0)
const flowStore = useFlowStore()
</script>
8 changes: 0 additions & 8 deletions apps/web-app/app/components/Navigation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ const partnerStore = usePartnerStore()
const kitchenStore = useKitchenStore()
const clientStore = useClientStore()
const ticketStore = useTicketStore()
const epicStore = useEpicStore()

const menus = computed(() => menuStore.menus.map((menu) => ({
label: menu.name,
Expand All @@ -65,13 +64,6 @@ const menuItems = computed(() => [
icon: 'i-lucide-contact-round',
active: route.path.startsWith('/staff'),
},
{
label: t('app.menu.epics'),
to: '/epic',
icon: 'i-lucide-crown',
active: route.path.startsWith('/epic'),
badge: epicStore.epics.length,
},
{
label: t('app.menu.tickets'),
to: '/ticket',
Expand Down
18 changes: 12 additions & 6 deletions apps/web-app/app/components/NotificationsDrawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,17 @@
</div>
</UCard>

<NotificationCard
v-for="notification in notificationStore.notifications"
:key="notification.id"
:notification="notification"
/>
<h2 class="pb-2 text-3xl font-bold">
Поток
</h2>

<div class="flex flex-col gap-3.5">
<FlowItemCard
v-for="item in flowStore.items"
:key="item.id"
:item="item"
/>
</div>
</div>
</div>
</template>
Expand All @@ -41,7 +47,7 @@ import { ModalAttachTelegram } from '#components'

const { isNotificationsOpened } = useApp()

const notificationStore = useNotificationStore()
const flowStore = useFlowStore()
const userStore = useUserStore()

const atriumBotId = 'lwleg6bka2oo61x5ot6zog6h'
Expand Down
60 changes: 60 additions & 0 deletions apps/web-app/app/stores/flow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { FlowItem, FlowItemComment, FlowItemView } from '@roll-stack/database'

export type FlowItemWithData = FlowItem & {
comments: FlowItemComment[]
views: FlowItemView[]
}

export const useFlowStore = defineStore('flow', () => {
const items = ref<FlowItemWithData[]>([])

const nowViewedItemsCount = computed(() => items.value.filter((item) => !item.views.some((view) => view.userId === useUserStore().id)).length)

async function update() {
try {
const data = await $fetch('/api/flow/list')
if (!data) {
return
}

items.value = data
} catch (error) {
if (error instanceof Error) {
if (error.message.includes('401')) {
// No
}
if (error.message.includes('404')) {
// Not found
}
}
}
}

async function addView(itemId: string) {
try {
await $fetch(`/api/flow/id/${itemId}/view`, {
method: 'POST',
})

await update()
} catch (error) {
if (error instanceof Error) {
if (error.message.includes('401')) {
// No
}
if (error.message.includes('404')) {
// Not found
}
}
}
}

return {
items,

nowViewedItemsCount,

update,
addView,
}
})
2 changes: 1 addition & 1 deletion apps/web-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
"@pinia/nuxt": "catalog:",
"@roll-stack/database": "workspace:*",
"@roll-stack/essence": "workspace:*",
"@roll-stack/schema": "workspace:*",
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 | 🔴 Critical

🧩 Analysis chain

Verify arktype dependency status.

The dependency addition is correct. However, the AI summary mentions "removed the arktype catalog dependency," but arktype is still imported and used in the server API files (e.g., import { type } from 'arktype' in comment.post.ts). Ensure arktype remains as a dependency if it's still being used.


🏁 Script executed:

#!/bin/bash
# Description: Check if arktype is still used in the codebase

echo "=== Searching for arktype imports ==="
rg -nP --type=ts "import.*from ['\"]arktype['\"]" apps/web-app/

echo ""
echo "=== Checking current dependencies ==="
cat apps/web-app/package.json | jq '.dependencies, .devDependencies'

Length of output: 5237


Declare arktype in package.json
Arktype is imported across multiple server and shared files but missing from apps/web-app/package.json dependencies—add "arktype": "<appropriate version>".

🤖 Prompt for AI Agents
In apps/web-app/package.json around line 21, the project imports arktype but it
is not declared as a dependency; add "arktype": "<appropriate version>" to the
dependencies object (use the repo workspace spec if this is a monorepo, e.g.
"workspace:*", or a specific semver like "^x.y.z"), save the file and run your
package manager (npm/yarn/pnpm) to update node_modules and the lockfile so
builds and imports resolve correctly.

"@roll-stack/ui": "workspace:*",
"arktype": "catalog:",
"aws4fetch": "catalog:",
"date-fns": "catalog:",
"ioredis": "catalog:",
Expand Down
41 changes: 41 additions & 0 deletions apps/web-app/server/api/flow/id/[itemId]/comment.post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { db } from '@roll-stack/database'
import { createFlowItemCommentSchema } from '@roll-stack/schema'
import { type } from 'arktype'

export default defineEventHandler(async (event) => {
try {
const itemId = getRouterParam(event, 'itemId')
if (!itemId) {
throw createError({
statusCode: 400,
message: 'Id is required',
})
}

const body = await readBody(event)
const data = createFlowItemCommentSchema(body)
if (data instanceof type.errors) {
throw data
}

// Guards:
// If not exist
const item = await db.flow.findItem(itemId)
if (!item) {
throw createError({
statusCode: 404,
message: 'Item not found',
})
}

await db.flow.createItemComment({
text: data.text,
itemId,
userId: event.context.user.id,
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 | 🟠 Major

Validate user context existence before access.

event.context.user.id is accessed without verifying that event.context.user exists. If authentication middleware doesn't populate this, it will cause a runtime error.

Add authentication guard:

+    if (!event.context.user) {
+      throw createError({
+        statusCode: 401,
+        message: 'Authentication required',
+      })
+    }
+
     await db.flow.createItemComment({
       text: data.text,
       itemId,
       userId: event.context.user.id,
     })
🤖 Prompt for AI Agents
In apps/web-app/server/api/flow/id/[itemId]/comment.post.ts around line 34, the
code accesses event.context.user.id without ensuring event.context.user exists;
add a guard that checks event.context and event.context.user before reading .id,
and return an appropriate authentication error (e.g., 401) or throw a clear
error when the user context is missing; update the handler flow so that after
the guard you can safely use userId and, if applicable, narrow the type (or
assert) to avoid TypeScript errors.

})

return { ok: true }
} catch (error) {
throw errorResolver(error)
}
})
Loading