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
8 changes: 4 additions & 4 deletions apps/atrium-telegram/app/components/Navigation.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div class="z-50 touch-pan-x sticky inset-0 h-38">
<div class="w-full h-14 px-4 py-0 flex flex-row flex-nowrap gap-0 items-start justify-center transition-all duration-200 ease-in-out">
<div class="z-50 touch-pan-x sticky inset-0 h-24">
<!-- <div class="w-full h-14 px-4 py-0 flex flex-row flex-nowrap gap-0 items-start justify-center transition-all duration-200 ease-in-out">
<UButton
v-if="isMainPage"
variant="solid"
Expand All @@ -13,7 +13,7 @@
leadingIcon: 'size-6 mx-auto',
}"
/>
</div>
</div> -->

<nav
v-if="isNavigationShown"
Expand All @@ -36,5 +36,5 @@
</template>

<script setup lang="ts">
const { isNavigationShown, mainRoutes, isMainPage } = useNavigation()
const { isNavigationShown, mainRoutes } = useNavigation()
</script>
11 changes: 11 additions & 0 deletions apps/atrium-telegram/app/components/SectionTitle.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<template>
<h2 class="text-2xl/6 font-bold tracking-tight">
{{ title }}
</h2>
</template>

<script setup lang="ts">
defineProps<{
title: string
}>()
</script>
12 changes: 11 additions & 1 deletion apps/atrium-telegram/app/components/flow/ItemCard.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
<template>
<ActiveCard>
<div class="flex flex-row gap-2 items-center">
<UIcon name="i-lucide-clipboard-check" class="size-8 text-primary" />
<UAvatar
v-if="item.userId"
:src="userAvatarUrl"
class="size-8"
/>
<UIcon
v-else
name="i-lucide-clipboard-check"
class="size-8 text-primary"
/>

<div v-if="!isViewed" class="flex flex-row items-center gap-1.5 text-error">
<UIcon
Expand Down Expand Up @@ -49,4 +58,5 @@ const { item } = defineProps<{

const userStore = useUserStore()
const isViewed = computed(() => item.views.some((view) => view.userId === userStore?.id))
const userAvatarUrl = computed(() => userStore.users.find((user) => user.id === item.userId)?.avatarUrl ?? undefined)
</script>
90 changes: 90 additions & 0 deletions apps/atrium-telegram/app/components/form/CreateFlowItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<template>
<UForm
:validate="createValidator(createFlowItemSchema)"
:state="state"
class="flex flex-col gap-3"
@submit="onSubmit"
>
<UFormField
:label="$t('common.title')"
name="title"
required
>
<UInput
v-model="state.title"
size="xl"
class="w-full"
/>
</UFormField>

<UFormField label="Текст" name="description">
<UTextarea
v-model="state.description"
placeholder="Основной текст поста"
autoresize
size="xl"
class="w-full"
/>
</UFormField>

<UButton
type="submit"
variant="solid"
color="secondary"
size="xl"
block
class="mt-3"
:label="$t('common.create')"
/>
</UForm>
</template>

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

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

const { t } = useI18n()
const { vibrate } = useFeedback()
const actionToast = useActionToast()

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

const state = ref<Partial<CreateFlowItem>>({
title: undefined,
description: undefined,
type: 'user_post',
userId: userStore.id,
})

async function onSubmit(event: FormSubmitEvent<CreateFlowItem>) {
const toastId = actionToast.start()
emit('submitted')

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

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

actionToast.success(toastId, t('toast.flow-item-created'))
vibrate('success')
emit('success')
} catch (error) {
console.error(error)
actionToast.error(toastId)
vibrate('error')
}
}
</script>
2 changes: 1 addition & 1 deletion apps/atrium-telegram/app/composables/useNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function _useNavigation() {
const mainRoutes = computed<NavigationRoute[]>(() => [
{
path: '/',
names: ['index', 'flow-itemId'],
names: ['index', 'flow-itemId', 'flow-new'],
title: t('app.flow'),
icon: 'i-lucide-waves',
exact: true,
Expand Down
4 changes: 1 addition & 3 deletions apps/atrium-telegram/app/pages/epic/[epicId]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
/>
</div>

<h1 class="text-2xl/6 font-bold">
{{ epic?.title }}
</h1>
<SectionTitle :title="epic?.title ?? ''" />

<div class="w-full text-base/5 whitespace-pre-wrap break-words">
{{ epic?.description }}
Expand Down
16 changes: 12 additions & 4 deletions apps/atrium-telegram/app/pages/flow/[itemId]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@
<PageContainer>
<Section>
<div class="flex flex-row items-start justify-between gap-2.5">
<UIcon name="i-lucide-clipboard-check" class="size-10 text-primary" />
<UAvatar
v-if="item?.userId"
:src="userAvatarUrl"
class="size-10"
/>
<UIcon
v-else
name="i-lucide-clipboard-check"
class="size-10 text-primary"
/>
</div>

<h1 class="text-2xl/6 font-bold">
{{ item?.title }}
</h1>
<SectionTitle :title="item?.title ?? ''" />

<div class="w-full text-base/5 whitespace-pre-wrap break-words">
{{ item?.description }}
Expand Down Expand Up @@ -61,6 +68,7 @@ const { params } = useRoute('flow-itemId')
const userStore = useUserStore()
const flowStore = useFlowStore()
const item = computed(() => flowStore.items.find((item) => item.id === params.itemId))
const userAvatarUrl = computed(() => userStore.users.find((user) => user.id === item.value?.userId)?.avatarUrl ?? undefined)

const isViewed = computed(() => item.value?.views.some((view) => view.userId === userStore?.id))

Expand Down
20 changes: 20 additions & 0 deletions apps/atrium-telegram/app/pages/flow/new.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<template>
<PageContainer>
<div class="flex flex-col gap-2.5">
<SectionTitle title="Создание поста" />

<FormCreateFlowItem @success="handleSuccess()" />
</div>
</PageContainer>
</template>

<script setup lang="ts">
definePageMeta({
name: 'flow-new',
canReturn: true,
})

function handleSuccess() {
return navigateTo('/')
}
</script>
20 changes: 12 additions & 8 deletions apps/atrium-telegram/app/pages/index.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
<template>
<PageContainer :back="false" class="flex flex-col gap-y-8">
<div class="flex flex-col gap-2.5">
<div class="text-2xl/6 font-bold tracking-tight">
Команда
</div>
<SectionTitle title="Команда" />
<StaffBlock />
</div>

<div class="flex flex-col gap-2.5">
<h1 class="text-2xl/6 font-bold tracking-tight">
Данные на сегодня
</h1>
<SectionTitle title="Данные на сегодня" />
<div class="grid grid-cols-2 gap-2">
<FlowKitchensOnline />
<FlowOrdersOnline />
Expand All @@ -20,8 +16,16 @@
</div>

<div class="flex flex-col gap-2.5">
<div class="text-2xl/6 font-bold tracking-tight">
Поток
<div class="flex flex-row justify-between items-center">
<SectionTitle title="Поток" />

<UButton
to="/flow/new"
variant="solid"
color="secondary"
icon="i-lucide-plus"
label="Создать пост"
/>
</div>
<div class="flex flex-col gap-4">
<NuxtLink
Expand Down
4 changes: 1 addition & 3 deletions apps/atrium-telegram/app/pages/navigation.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
<template>
<PageContainer>
<h1 class="text-2xl/6 font-bold tracking-tight">
В работе
</h1>
<SectionTitle title="В работе" />
</PageContainer>
</template>
4 changes: 1 addition & 3 deletions apps/atrium-telegram/app/pages/no-auth.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<template>
<PageContainer :back="false">
<h1 class="text-2xl">
Нет доступа!
</h1>
<SectionTitle title="Нет доступа!" />
<p>Напишите в поддержку.</p>
</PageContainer>
</template>
4 changes: 1 addition & 3 deletions apps/atrium-telegram/app/pages/tasks/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
/>

<div class="flex flex-col gap-1">
<h1 class="text-2xl/6 font-bold tracking-tight">
{{ userStore.name }}, привет!
</h1>
<SectionTitle :title="`${userStore.name}, привет!`" />
<p class="text-base/5">
<template v-if="taskStore.myTodayTasks.length">
Сегодня по плану еще
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
/>
</div>

<h1 class="text-2xl/6 font-bold">
{{ ticket?.title }}
</h1>
<SectionTitle :title="ticket?.title ?? ''" />

<div class="w-full text-base/5 whitespace-pre-wrap break-words">
{{ ticket?.description }}
Expand Down
4 changes: 1 addition & 3 deletions apps/atrium-telegram/app/pages/ticket/index.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
<template>
<PageContainer>
<div class="flex flex-col gap-2.5">
<div class="text-2xl/6 font-bold tracking-tight">
Активные тикеты
</div>
<SectionTitle title="Активные тикеты" />
<div class="flex flex-col gap-4">
<NuxtLink
v-for="ticket of ticketStore.tickets"
Expand Down
3 changes: 2 additions & 1 deletion apps/atrium-telegram/i18n/locales/ru-RU.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
"epic-created": "Эпик создан",
"epic-updated": "Эпик обновлен",
"epic-deleted": "Эпик удален",
"beacon-created": "Маяк создан"
"beacon-created": "Маяк создан",
"flow-item-created": "Пост создан"
}
}
48 changes: 48 additions & 0 deletions apps/atrium-telegram/server/api/flow/index.post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { createFlowItemSchema } from '#shared/services/flow'
import { repository } from '@roll-stack/database'
import { type } from 'arktype'

export default defineEventHandler(async (event) => {
try {
const body = await readBody(event)
const data = createFlowItemSchema(body)
if (data instanceof type.errors) {
throw data
}

const item = await repository.flow.createItem({
type: data.type,
title: data.title,
description: data.description,
userId: data.userId,
})
Comment on lines +13 to +18

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

Do not trust client-supplied userId; derive from authenticated context.

Accepting userId from the request body enables impersonation. Use event.context.user.id and ignore body.userId.

Apply this diff:

-    const item = await repository.flow.createItem({
-      type: data.type,
-      title: data.title,
-      description: data.description,
-      userId: data.userId,
-    })
+    const userId = event.context.user?.id
+    if (!userId) {
+      throw createError({ statusCode: 401, statusMessage: 'Unauthorized' })
+    }
+    const item = await repository.flow.createItem({
+      type: data.type,
+      title: data.title,
+      description: data.description,
+      userId,
+    })
📝 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 item = await repository.flow.createItem({
type: data.type,
title: data.title,
description: data.description,
userId: data.userId,
})
const userId = event.context.user?.id
if (!userId) {
throw createError({ statusCode: 401, statusMessage: 'Unauthorized' })
}
const item = await repository.flow.createItem({
type: data.type,
title: data.title,
description: data.description,
userId,
})
🤖 Prompt for AI Agents
In apps/atrium-telegram/server/api/flow/index.post.ts around lines 13 to 18, the
code is using client-supplied data.userId when creating a flow item which allows
impersonation; instead derive the user ID from the authenticated context
(event.context.user.id) and ignore any userId in the request body. Update the
createItem call to pass the authenticated user id, validate that
event.context.user and event.context.user.id exist (return 401/400 if missing),
and remove or ignore usage of data.userId in this handler so only the
server-derived user ID is stored.

if (!item) {
throw createError({
statusCode: 500,
message: 'Item not created',
})
}

// Bot notification in chat
// if (list.chat) {
// const bot = await repository.chat.findNotificationBot(list.chat.id)
// if (bot) {
// const text = `${event.context.user.name} ${event.context.user.surname} ${suffixByGender(['создал', 'создала'], event.context.user.gender)} задачу «${task.name}»`

// // Send message as bot
// await repository.chat.createMessage({
// chatId: list.chat.id,
// userId: bot.user.id,
// text,
// })
// }
// }

return {
ok: true,
result: item,
}
} catch (error) {
throw errorResolver(error)
}
})
11 changes: 11 additions & 0 deletions apps/atrium-telegram/shared/services/flow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { type } from 'arktype'

const flowTypeSchema = type('"user_post" | "daily_task_report" | "weekly_task_report"')

export const createFlowItemSchema = type({
title: type('2 <= string <= 150').describe('error.length.invalid'),
description: type('string <= 1500 | undefined').describe('error.length.invalid').optional(),
type: flowTypeSchema.describe('error.length.invalid'),
userId: type('string | undefined').describe('error.length.invalid').optional(),
})
export type CreateFlowItem = typeof createFlowItemSchema.infer
2 changes: 1 addition & 1 deletion apps/web-app/app/components/CheckoutCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div class="mb-2 flex flex-row justify-between items-center">
<div class="flex flex-row gap-3 items-center">
<img
:src="`https://avatar.nextorders.ru/${checkout?.clientId}?emotion=8`"
:src="`https://avatar.nextorders.ru/${checkout?.id}?emotion=8`"
width="40"
height="40"
alt=""
Expand Down
Loading