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
2 changes: 1 addition & 1 deletion apps/atrium-telegram/app/pages/task/[taskId]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
</p>
</div>

<div class="mt-6 flex flex-col gap-0.5">
<div class="mt-6 flex flex-col gap-1">
<div class="text-sm text-muted">
Создана
<time
Expand Down
63 changes: 49 additions & 14 deletions apps/atrium-telegram/app/pages/task/my.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
:class="[
taskStore.isTodayOnly ? 'tg-text' : 'text-secondary',
]"
@click="taskStore.isTodayOnly = !taskStore.isTodayOnly"
@click="() => {
taskStore.isTodayOnly = !taskStore.isTodayOnly
taskStore.isInFlowMode = false
}"
>
{{ taskStore.myTodayTasks.length }} {{ pluralizationRu(taskStore.myTodayTasks.length, ['задача', 'задачи', 'задач']) }}
</ULink>.
Expand All @@ -26,22 +29,52 @@

<template v-if="taskStore.isInitialized">
<Section>
<TasksTodaySwitch />
<USwitch
v-model="taskStore.isInFlowMode"
size="xl"
color="secondary"
:label="taskStore.isInFlowMode ? `Режим &quot;Поток&quot;` : 'Режим &quot;Списки&quot;'"
:ui="{
root: 'items-center',
label: 'ml-1.5 text-base/5 font-semibold',
}"
@change="vibrate()"
/>
</Section>

<TaskList
v-for="taskList in taskStore.myLists"
:key="taskList.id"
:list-id="taskList.id"
:current-user-id="userStore.id as string"
/>
<div v-if="!taskStore.isInFlowMode" class="flex flex-col gap-8">
<TaskList
v-for="taskList in taskStore.myLists"
:key="taskList.id"
:list-id="taskList.id"
:current-user-id="userStore.id as string"
/>

<CreateCard
v-if="!taskStore.isTodayOnly"
:label="$t('app.create.task-list.button')"
icon="i-lucide-list-todo"
@click="modalCreateTaskList.open()"
/>
<CreateCard
v-if="!taskStore.isTodayOnly"
:label="$t('app.create.task-list.button')"
icon="i-lucide-list-todo"
@click="modalCreateTaskList.open()"
/>
</div>

<div v-else>
<div
v-if="taskStore.myTasksOrderedByDate.length"
class="w-full flex flex-col gap-2.5"
>
<TaskActiveCard
v-for="task in taskStore.myTasksOrderedByDate"
:key="task.id"
:task="task"
/>
</div>
<template v-else>
<p class="text-center text-base text-muted">
Активных задач нет
</p>
</template>
</div>
</template>
<div v-else>
<div class="py-4 w-full flex justify-center">
Expand All @@ -58,6 +91,8 @@ definePageMeta({
name: 'my-tasks',
})

const { vibrate } = useFeedback()

const overlay = useOverlay()
const modalCreateTaskList = overlay.create(ModalCreateTaskList)

Expand Down
40 changes: 40 additions & 0 deletions apps/atrium-telegram/app/stores/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ type TaskListWithData = TaskList & {
export const useTaskStore = defineStore('task', () => {
const lists = ref<TaskListWithData[]>([])
const tasks = ref<Task[]>([])
const notCompletedTasks = ref<Task[]>([])
const isTodayOnly = ref(false)
const isInFlowMode = ref(false)
const isInitialized = ref(false)

const userStore = useUserStore()
Expand All @@ -25,6 +27,16 @@ export const useTaskStore = defineStore('task', () => {
).filter((taskList) => isTodayOnly.value ? 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 myTasksOrderedByDate = computed(() => {
const filterByMe = (task: Task) => task.performerId === userStore.id
const sortByDateAsc = (a: Task, b: Task) => a.date && b.date ? new Date(a.date).getTime() - new Date(b.date).getTime() : 0

const myTasks = notCompletedTasks.value.filter(filterByMe)
const tasksWithDate = myTasks.filter((task) => task.date).sort(sortByDateAsc)
const tasksWithoutDate = myTasks.filter((task) => !task.date)

return [...tasksWithDate, ...tasksWithoutDate]
})

const initDataRaw = useSignal(_initDataRaw)

Expand All @@ -44,6 +56,7 @@ export const useTaskStore = defineStore('task', () => {
isInitialized.value = true

await updateCompleted()
await updateNotCompleted()
} catch (error) {
if (error instanceof Error) {
if (error.message.includes('401')) {
Expand Down Expand Up @@ -80,6 +93,30 @@ export const useTaskStore = defineStore('task', () => {
}
}

async function updateNotCompleted() {
try {
const data = await $fetch('/api/task/list/not-completed', {
headers: {
Authorization: `tma ${initDataRaw.value}`,
},
})
if (!data) {
return
}

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

async function setAsFocused(taskId: string) {
try {
await $fetch(`/api/task/id/${taskId}/focus`, {
Expand Down Expand Up @@ -117,11 +154,14 @@ export const useTaskStore = defineStore('task', () => {
return {
lists,
tasks,
notCompletedTasks,
isTodayOnly,
isInFlowMode,
isInitialized,

myLists,
myTodayTasks,
myTasksOrderedByDate,

update,
setAsFocused,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { db } from '@roll-stack/database'

export default defineEventHandler(async () => {
return db.task.findAllNotCompleted()
})
Comment on lines +1 to +5
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

Route returns all users’ active tasks; enforce auth and scope.

The handler exposes every not-completed task. Read the user from the TMA Authorization header/context and query only their tasks (or tasks in lists they can access). Also prefer return await for better stack traces.

Example:

-import { db } from '@roll-stack/database'
-
-export default defineEventHandler(async () => {
-  return db.task.findAllNotCompleted()
-})
+import { db } from '@roll-stack/database'
+// import your existing auth util; adjust to your codebase
+// e.g., const user = await requireUser(event) or readTelegramUser(event)
+export default defineEventHandler(async (event) => {
+  const user = await requireUser(event) // adapt to your auth helper
+  return await db.task.findAllNotCompletedByPerformer(user.id)
+})

If you already attach the user to event.context, use it directly. Consider projecting only needed fields to reduce payload.

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


⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify no other routes expose unscoped task listings.

Search for unscoped usages to avoid similar leaks.


🏁 Script executed:

#!/bin/bash
# Find unscoped task list endpoints/usages
rg -nP -C2 'findAllNotCompleted\(|completedAt\s*[,)]\s*=>\s*isNull' apps/ packages/ | sed -n '1,200p'
# Look for auth helpers to wire here
rg -nP -C2 'requireUser\(|getUser.*event|telegram|initData|tma ' --type=ts -- apps/ | sed -n '1,200p'

Length of output: 15076



🏁 Script executed:

#!/bin/bash
# Check atrium-telegram app structure and auth
fd '(middleware|auth)' apps/atrium-telegram/server/ -type f
# Look for any existing auth in atrium-telegram
rg -l 'getUserSession|validateTelegramData|requireUser|getHeader' apps/atrium-telegram/server/ --type=ts

Length of output: 220



🏁 Script executed:

#!/bin/bash
# Read atrium-telegram auth middleware to identify auth pattern
cat apps/atrium-telegram/server/middleware/01.auth.ts

Length of output: 1894


Scoped task listing is protected by auth middleware but still returns unscoped results—specify userId scope.

The endpoint is covered by atrium-telegram's auth middleware (which enforces valid Telegram user), but findAllNotCompleted() returns all incomplete tasks regardless of the authenticated user. Scope results to event.context.user.id or add a userId parameter to the database method.

No other routes expose findAllNotCompleted() (grep confirmed single usage).

🤖 Prompt for AI Agents
In apps/atrium-telegram/server/api/task/list/not-completed.get.ts lines 1-5, the
handler calls db.task.findAllNotCompleted() and returns all users' incomplete
tasks despite the route being protected by auth; change the handler to scope
results to the authenticated user by reading the id from event.context.user.id
and passing it into the DB call (e.g., db.task.findAllNotCompleted({ userId:
event.context.user.id })), or if the DB API lacks that param, add a userId
argument to findAllNotCompleted and update its query to filter by userId before
returning results.

7 changes: 7 additions & 0 deletions packages/database/src/repository/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ export class Task {
})
}

static async findAllNotCompleted() {
return useDatabase().query.tasks.findMany({
where: (tasks, { isNull }) => isNull(tasks.completedAt),
orderBy: (tasks, { desc }) => desc(tasks.updatedAt),
})
}

static async findList(id: string) {
return useDatabase().query.taskLists.findFirst({
where: (taskLists, { eq, and }) => and(eq(taskLists.id, id), eq(taskLists.isArchived, false)),
Expand Down