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
1 change: 1 addition & 0 deletions apps/web-app/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ NUXT_AI_MODEL=
NUXT_AI_MODEL_PRO=
NUXT_AI_SERVICE_TOKEN=
NUXT_AI_DAILY_REPORT_PROMPT=
NUXT_AI_WEEKLY_REPORT_PROMPT=

# Telegram
NUXT_TELEGRAM_LOCAL_BOT_API_SERVER_URL=
Expand Down
2 changes: 2 additions & 0 deletions apps/web-app/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default defineNuxtConfig({
apiKey: '',
serviceToken: '',
dailyReportPrompt: '',
weeklyReportPrompt: '',
},
telegram: {
localBotApiServerUrl: '',
Expand Down Expand Up @@ -81,6 +82,7 @@ export default defineNuxtConfig({
'0 * * * *': ['kitchen:revenue-update'], // Every hour
'0 0 * * *': ['kitchen:rating-update'], // Every day
'0 17 * * 1-5': ['ai:daily-report'], // Mon-Fri 17:00
'30 17 * * 5': ['ai:weekly-report'], // Friday 17:30
},
},
tiptap: {
Expand Down
4 changes: 3 additions & 1 deletion apps/web-app/server/tasks/ai/daily-report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const logger = useLogger('task:ai:daily-report')
export default defineTask({
meta: {
name: 'ai:daily-report',
description: 'Prepare and post daily report to Telegram group',
description: 'Prepare and post daily report',
},
async run() {
if (process.env.NODE_ENV !== 'production') {
Expand All @@ -31,6 +31,8 @@ export default defineTask({
const client = new OpenAI({
apiKey: ai.apiKey,
baseURL: ai.baseUrl,
timeout: 120000,
maxRetries: 2,
})

const response = await client.chat.completions.create({
Expand Down
91 changes: 91 additions & 0 deletions apps/web-app/server/tasks/ai/weekly-report.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import process from 'node:process'
import { repository } from '@roll-stack/database'
import { format } from 'date-fns'
import { ru } from 'date-fns/locale/ru'
import OpenAI from 'openai'

const logger = useLogger('task:ai:weekly-report')

export default defineTask({
meta: {
name: 'ai:weekly-report',
description: 'Prepare and post weekly report',
},
async run() {
if (process.env.NODE_ENV !== 'production') {
logger.info('Skipping task in non-production environment')
return { result: true }
}

try {
const { ai } = useRuntimeConfig()

const tasks = await repository.task.listCompletedThisWeek()
const staff = await repository.user.findStaff()

function preparePerformer(performerId: string | null) {
const user = staff.find((user) => user.id === performerId)
if (!user) {
return null
}

return {
name: user?.name,
surname: user?.surname,
caption: user?.caption,
completedTasksCount: tasks.filter((task) => task.performerId === user?.id).length,
}
}

const preparedTasks = tasks.map((task) => {
const performer = preparePerformer(task.performerId)

return {
completedAt: task.completedAt,
name: task.name,
description: task.description,
report: task.report,
performer,
}
})

const client = new OpenAI({
apiKey: ai.apiKey,
baseURL: ai.baseUrl,
timeout: 120000,
maxRetries: 2,
})

const response = await client.chat.completions.create({
model: ai.modelPro,
messages: [
{
role: 'system',
content: ai.weeklyReportPrompt,
},
{
role: 'user',
content: JSON.stringify(preparedTasks),
},
Comment on lines +67 to +69
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

PII to third‑party AI: anonymize or gate behind config.

preparedTasks includes names/surnames/captions. Add an opt‑in (e.g., runtimeConfig.ai.allowPII) or hash/initials before sending.

Example guard:

if (!ai.allowPII) {
  preparedTasks.forEach(t => {
    if (t.performer) {
      t.performer = {
        ...t.performer,
        name: undefined,
        surname: undefined,
        caption: undefined,
      }
    }
  })
}
🤖 Prompt for AI Agents
In apps/web-app/server/tasks/ai/weekly-report.ts around lines 67-69, the code
sends preparedTasks (which may contain names, surnames, captions) to a
third-party AI; add a runtime guard or anonymization so PII is not sent by
default. Implement a check like runtimeConfig.ai.allowPII (opt-in) and if false
iterate preparedTasks to remove or replace performer.name, performer.surname and
performer.caption (or replace with initials/hash) before stringifying and
sending to the AI endpoint. Ensure the guard is applied immediately before
building the message payload so no PII is included unless explicitly allowed.

],
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const finalMessage = response.choices[0].message.content
if (!finalMessage) {
return { result: true }
}

// Flow item
const week = format(new Date(), 'w', { locale: ru })
await repository.flow.createItem({
type: 'weekly_task_report',
title: `Задачи за неделю ${week}`,
description: finalMessage,
})
} catch (error) {
errorResolver(error)
}

return { result: true }
Comment on lines +85 to +89
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

Don’t mask failures; propagate to the scheduler.

Currently returns success after errors, hiding incidents.

Apply:

-    } catch (error) {
-      errorResolver(error)
-    }
-
-    return { result: true }
+    } catch (error) {
+      errorResolver(error)
+      throw error
+    }
+
+    return { result: true }
📝 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
} catch (error) {
errorResolver(error)
}
return { result: true }
} catch (error) {
errorResolver(error)
throw error
}
return { result: true }
🤖 Prompt for AI Agents
In apps/web-app/server/tasks/ai/weekly-report.ts around lines 85 to 89, the
catch block swallows errors and the function always returns success which hides
failures from the scheduler; change the catch to await/errorResolver(error) if
it returns a promise (or call it synchronously if not) and then rethrow the
caught error so the scheduler receives the failure (do not return { result: true
} after an exception).

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

static async listCompletedThisWeek() {
return useDatabase().query.tasks.findMany({
where: (tasks, { gte }) => gte(tasks.completedAt, sql`now() - interval '6 day'`),
orderBy: (tasks, { desc }) => desc(tasks.updatedAt),
})
}

static async autoCreatorsList() {
return useDatabase().query.taskAutoCreators.findMany()
}
Expand Down
2 changes: 1 addition & 1 deletion packages/database/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export type TimeZone = '+00:00'
| '+11:00'
| '+12:00'

export type FlowItemType = 'daily_task_report'
export type FlowItemType = 'daily_task_report' | 'weekly_task_report'

export type Permission = InferSelectModel<typeof tables.permissions>
export type PermissionDraft = InferInsertModel<typeof tables.permissions>
Expand Down