Skip to content

feat: webinar app#181

Merged
hmbanan666 merged 4 commits into
mainfrom
webinar
Sep 25, 2025
Merged

feat: webinar app#181
hmbanan666 merged 4 commits into
mainfrom
webinar

Conversation

@hmbanan666
Copy link
Copy Markdown
Collaborator

@hmbanan666 hmbanan666 commented Sep 25, 2025

Summary by CodeRabbit

  • New Features

    • Added a new “Webinar” app with Russian locale, themed UI, landing page (video hero, scrolling titles divider, feature sections), header logo, improved error page, and global typography.
  • Bug Fixes

    • Form submissions now automatically associate with the signed-in user (userId no longer required in form payload).
  • Chores

    • CI pipelines updated to include Webinar in nightly/release builds; workspace dependency bumps (Nuxt UI v4, icon sets) and container artifacts added.

@hmbanan666 hmbanan666 self-assigned this Sep 25, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Sep 25, 2025

Walkthrough

Adds a new "webinar" Nuxt app (config, pages, components, i18n, CSS, Docker, package/tsconfig) and includes it in CI matrices; removes client-side userId from atrium-telegram form/schema and sources userId server-side from request context; centralizes database entity types and adjusts queue initialization wiring.

Changes

Cohort / File(s) Summary
CI workflows (add webinar to matrices)
\.github/workflows/docker-nightly.yml, \.github/workflows/docker-release.yml
Add webinar to APPS arrays / matrices used by nightly and release workflows.
Atrium Telegram: flow creation uses server context userId
apps/atrium-telegram/app/components/form/CreateFlowItem.vue, apps/atrium-telegram/server/api/flow/index.post.ts, apps/atrium-telegram/shared/services/flow.ts
Remove userId from client initial data/schema; server handler now assigns userId from event.context.user.id when creating flow items.
Webinar app: project and build config
apps/webinar/nuxt.config.ts, apps/webinar/package.json, apps/webinar/tsconfig.json, docker/webinar/Dockerfile, docker/webinar/health-check.sh, pnpm-workspace.yaml
Add Nuxt app config, package metadata/scripts/deps, TS references, Docker multi-stage build and health-check script, and bump workspace deps (iconify-json, Nuxt UI).
Webinar app: runtime UI and assets
apps/webinar/app/app.vue, apps/webinar/app/app.config.ts, apps/webinar/app/assets/css/styles.css
Add app shell (locale/head binding), define UI tokens via defineAppConfig, and global heading font CSS.
Webinar app: components, pages, i18n
apps/webinar/app/components/HeaderLogo.vue, apps/webinar/app/components/InfiniteTitlesDivider.vue, apps/webinar/app/pages/index.vue, apps/webinar/app/error.vue, apps/webinar/i18n/locales/ru-RU.json
Add header logo, infinite titles divider carousel, landing page, error page SFCs, and Russian locale strings.
Database: centralize entity types & re-export types
packages/database/src/types/entities.ts, packages/database/src/types/index.ts, packages/database/src/types/tables.ts, packages/database/src/tables.ts
Introduce entities.ts string-union types, update tables to reference entities.*, add types index, and reduce exported aliases in tables-types file.
Queue: defer declare functions into connection init
packages/queue/src/connection.ts, packages/queue/src/exchanges.ts, packages/queue/src/queues.ts
Move exchange/queue declaration logic into connection initialization helpers; remove exported declare* functions and adjust imports/initialization flow.
Editor settings
.vscode/settings.json
Add findUnusedExports.detectCircularImports: true setting.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Atrium Telegram Client
  participant API as POST /api/flow
  participant Auth as Request Context
  participant DB as Database

  Client->>API: POST { flow data } (no userId)
  API->>Auth: read event.context.user.id
  Auth-->>API: userId
  API->>DB: insert flow { ...data, userId }
  DB-->>API: created record
  API-->>Client: 201 Created
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Poem

I hop and patch the pipeline bright,
A webinar sprouts into the night.
Clients drop IDs, servers keep the key,
Docker warms the hosting tree.
Tail wags, queues align — a bunny's deploy glee! 🐰✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly captures the primary change of the pull request—adding the new webinar application—without extraneous details or noise, making it clear and directly related to the extensive app, configuration, and pipeline updates in the changeset.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch webinar

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@socket-security
Copy link
Copy Markdown

socket-security Bot commented Sep 25, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​iconify-json/​simple-icons@​1.2.51 ⏵ 1.2.531001007796 +1100
Updated@​iconify-json/​fluent@​1.2.30 ⏵ 1.2.3193 +210077 +295 +1100
Updated@​iconify-json/​lucide@​1.2.67 ⏵ 1.2.68100 +110079 +294 -1100
Updated@​nuxt/​ui@​3.3.3 ⏵ 4.0.0100 +3100100100 +2100

View full report

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/atrium-telegram/server/api/flow/index.post.ts (1)

13-18: Guard against missing event.context.user before dereferencing.

If an unauthenticated request hits this endpoint (or the middleware ever fails to populate the user), event.context.user will be undefined and the API will throw a TypeError, returning a 500 instead of a proper 401/403. Please validate the session before reading id.

-    const item = await repository.flow.createItem({
+    const user = event.context.user
+    if (!user?.id) {
+      throw createError({
+        statusCode: 401,
+        message: 'Unauthorized',
+      })
+    }
+
+    const item = await repository.flow.createItem({
       type: data.type,
       title: data.title,
       description: data.description,
-      userId: event.context.user.id,
+      userId: user.id,
     })
🧹 Nitpick comments (13)
apps/webinar/app/assets/css/styles.css (1)

1-3: Add a robust fallback font stack

Use a fallback in case --font-headers isn’t defined.

 h1, h2, h3, h4, h5, h6 {
-  font-family: var(--font-headers);
+  font-family: var(--font-headers, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji");
 }
pnpm-workspace.yaml (2)

56-56: Session hardening for nuxt-auth-utils

Ensure NUXT_SESSION_PASSWORD (>=32 chars) is set in production and cookie options (sameSite, secure) are aligned with prod domains.


11-14: Nuxt UI v4 props – auto-scroll unchanged; update UButton defaults

  • apps/webinar/app/components/InfiniteTitlesDivider.vue:6-8 – :auto-scroll usage remains valid.
  • UButton’s variant/size props still exist, but v4 dropped the implicit md default size—add size="md" where omitted or adjust your appConfig/ui theme’s defaultVariants.
apps/webinar/package.json (2)

6-14: Make preview script cross‑platform

Use CLI flag instead of env var assignment (breaks on Windows).

-    "preview": "PORT=3501 nuxt preview",
+    "preview": "nuxt preview --port 3501",

1-30: Optional: declare Node engine

Helps CI/dev parity for Nuxt 4.

 {
   "name": "@roll-stack/webinar",
   "type": "module",
   "version": "0.0.0",
   "private": true,
+  "engines": {
+    "node": ">=20.10.0"
+  },
apps/webinar/app/components/InfiniteTitlesDivider.vue (3)

18-22: Mark decorative image as hidden from AT; consider lazy loading

Improves accessibility/perf.

-      <img
+      <img
         src="/sushi-heart.svg"
-        alt=""
-        class="w-10 h-auto opacity-15"
+        alt=""
+        aria-hidden="true"
+        role="presentation"
+        loading="lazy"
+        class="w-10 h-auto opacity-15"
       >

10-12: Validate utility classes with your CSS engine

min-w-120 and opacity-15 are not Tailwind defaults. If not using UnoCSS or custom theme, switch to Tailwind-compatible utilities.

Possible Tailwind equivalents:

  • min-w-120 -> min-w-[30rem]
  • opacity-15 -> opacity-[0.15]
-        item: 'shrink-0 basis-full md:basis-1/3 min-w-120 h-full min-h-16 flex flex-row gap-4 items-center justify-around',
+        item: 'shrink-0 basis-full md:basis-1/3 min-w-[30rem] h-full min-h-16 flex flex-row gap-4 items-center justify-around',

27-33: Externalize visible strings to i18n

Hard-coded Russian strings bypass your i18n. Prefer $t keys here.

Do you want a follow-up patch to move these into keys like webinar.carousel.[0..3] and update the locale file?

apps/webinar/app/app.vue (2)

16-18: Harden locale lookups with a safe fallback and de-dup usage.

If locales[locale.value] is missing, this will throw. Define a single uiLocale with a fallback and derive lang/dir from it.

Apply this diff:

-const lang = computed(() => locales[locale.value].code)
-const dir = computed(() => locales[locale.value].dir)
+const uiLocale = computed(() => locales[locale.value] ?? locales.en)
+const lang = computed(() => uiLocale.value.code)
+const dir = computed(() => uiLocale.value.dir)

Update the template binding accordingly (see next comment).


2-4: Use the computed locale with fallback in the template.

This keeps template/script aligned and avoids undefined lookups.

Apply this diff:

-  <UApp
-    :locale="locales[locale]"
+  <UApp
+    :locale="uiLocale"
     class="min-h-svh"
   >
apps/webinar/app/pages/index.vue (2)

16-23: Video: reduce eager bandwidth and add basic a11y.

Prevent heavy autoplay prefetch and add a descriptive label.

Apply this diff:

-    <video
-      controls
+    <video
+      controls preload="metadata" playsinline aria-label="Видео-превью вебинара"
       class="max-h-160 w-auto mx-auto rounded-xl"
     >

6-45: Consider moving hardcoded Russian strings to i18n.

You’ve configured i18n; externalize hero/section text to locale files and use t() to keep translation scalable.

apps/webinar/nuxt.config.ts (1)

3-3: nuxt-auth-utils: ensure session password in production.

Set NUXT_SESSION_PASSWORD (>=32 chars) or runtimeConfig.session.password for secure cookie encryption.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 20e0b5d and cbc6094.

⛔ Files ignored due to path filters (9)
  • apps/webinar/public/android-chrome-192x192.png is excluded by !**/*.png
  • apps/webinar/public/android-chrome-512x512.png is excluded by !**/*.png
  • apps/webinar/public/apple-touch-icon.png is excluded by !**/*.png
  • apps/webinar/public/favicon-16x16.png is excluded by !**/*.png
  • apps/webinar/public/favicon-32x32.png is excluded by !**/*.png
  • apps/webinar/public/favicon.ico is excluded by !**/*.ico
  • apps/webinar/public/sushi-heart.svg is excluded by !**/*.svg
  • apps/webinar/public/sushi-main-logo.png is excluded by !**/*.png
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • .github/workflows/docker-nightly.yml (1 hunks)
  • .github/workflows/docker-release.yml (1 hunks)
  • apps/atrium-telegram/app/components/form/CreateFlowItem.vue (0 hunks)
  • apps/atrium-telegram/server/api/flow/index.post.ts (1 hunks)
  • apps/atrium-telegram/shared/services/flow.ts (0 hunks)
  • apps/webinar/app/app.config.ts (1 hunks)
  • apps/webinar/app/app.vue (1 hunks)
  • apps/webinar/app/assets/css/styles.css (1 hunks)
  • apps/webinar/app/components/HeaderLogo.vue (1 hunks)
  • apps/webinar/app/components/InfiniteTitlesDivider.vue (1 hunks)
  • apps/webinar/app/error.vue (1 hunks)
  • apps/webinar/app/pages/index.vue (1 hunks)
  • apps/webinar/i18n/locales/ru-RU.json (1 hunks)
  • apps/webinar/nuxt.config.ts (1 hunks)
  • apps/webinar/package.json (1 hunks)
  • apps/webinar/tsconfig.json (1 hunks)
  • docker/webinar/Dockerfile (1 hunks)
  • docker/webinar/health-check.sh (1 hunks)
  • pnpm-workspace.yaml (1 hunks)
💤 Files with no reviewable changes (2)
  • apps/atrium-telegram/app/components/form/CreateFlowItem.vue
  • apps/atrium-telegram/shared/services/flow.ts
⏰ 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 (8)
docker/webinar/health-check.sh (1)

1-2: Health probe fits the container contract.

Straightforward curl check against port 3000 matches the app defaults and will trip the Docker health check correctly.

.github/workflows/docker-nightly.yml (1)

29-29: Matrix update looks good.

Including webinar keeps the nightly build matrix consistent with the new app directory.

.github/workflows/docker-release.yml (1)

36-36: Release whitelist stays in sync.

Adding webinar here ensures the release workflow recognizes the new package tag without extra changes.

docker/webinar/Dockerfile (1)

1-48: Docker image pipeline is solid.

Build/install steps, non-root user, and health check wiring all look correct for the Nuxt output you’re copying over.

apps/webinar/tsconfig.json (1)

1-9: LGTM

References setup is standard for Nuxt TS projects.

apps/webinar/app/app.config.ts (1)

1-60: LGTM — cohesive UI tokens and sensible variants.

Good defaults (button/tabs/modal/toast/card). Works well with the app’s theme.

apps/webinar/nuxt.config.ts (2)

41-46: No action needed: i18n is enabled via @roll-stack/ui extend.


37-40: Set colorMode.preference to "light" in nuxt.config.ts and drop the runtime watcher
preference is supported in the Nuxt Color Mode config—configure the initial theme here instead of using the app.vue watcher.

Comment thread apps/webinar/app/app.vue Outdated
Comment on lines +28 to +35
watch(colorMode, () => {
const colorModeStorage = localStorage.getItem('color-mode')

if (colorMode.value === 'system' || colorModeStorage === 'system') {
colorMode.value = 'light'
localStorage.setItem('color-mode', 'light')
}
})
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

System theme “fix” won’t trigger; also risks SSR/localStorage usage.

Watching the reactive object (colorMode) doesn’t fire on internal property changes. This code likely never runs, and even if it did, directly touching localStorage without a client guard is risky in SSR. Watch colorMode.preference (or value) with immediate: true, guard for client, and set preference instead of value.

Apply this diff:

-const colorMode = useColorMode()
-watch(colorMode, () => {
-  const colorModeStorage = localStorage.getItem('color-mode')
-
-  if (colorMode.value === 'system' || colorModeStorage === 'system') {
-    colorMode.value = 'light'
-    localStorage.setItem('color-mode', 'light')
-  }
-})
+const colorMode = useColorMode()
+watch(
+  () => colorMode.preference,
+  (pref) => {
+    if (!process.client) return
+    if (pref === 'system' || localStorage.getItem('color-mode') === 'system') {
+      colorMode.preference = 'light'
+    }
+  },
+  { immediate: 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
watch(colorMode, () => {
const colorModeStorage = localStorage.getItem('color-mode')
if (colorMode.value === 'system' || colorModeStorage === 'system') {
colorMode.value = 'light'
localStorage.setItem('color-mode', 'light')
}
})
const colorMode = useColorMode()
watch(
() => colorMode.preference,
(pref) => {
if (!process.client) return
if (pref === 'system' || localStorage.getItem('color-mode') === 'system') {
colorMode.preference = 'light'
}
},
{ immediate: true }
)
🤖 Prompt for AI Agents
In apps/webinar/app/app.vue around lines 28 to 35, the watch is observing the
reactive root (`colorMode`) and touching localStorage directly which may never
trigger and is unsafe in SSR; change the watcher to observe the actual property
(e.g., colorMode.preference or colorMode.value depending on the reactive
structure), add { immediate: true } so it runs on init, wrap any access to
localStorage in a client guard (if (typeof window !== 'undefined') or
process.client) and when forcing the system fallback set the preference property
(e.g., colorMode.preference = 'light') rather than reassigning the whole
reactive value, and persist to localStorage inside the client guard.

Comment on lines +2 to +6
<img
src="/sushi-main-logo.png"
alt=""
class="mx-auto h-20 w-auto motion-preset-pop"
>
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

Logo needs accessible alt text; consider linking to home

Empty alt hides a meaningful image from screen readers; header logos should be descriptive. Also, wrapping with NuxtLink improves UX.

-  <img
-    src="/sushi-main-logo.png"
-    alt=""
-    class="mx-auto h-20 w-auto motion-preset-pop"
-  >
+  <NuxtLink to="/" aria-label="Home">
+    <img
+      src="/sushi-main-logo.png"
+      alt="Webinar logo"
+      class="mx-auto h-20 w-auto motion-preset-pop"
+    >
+  </NuxtLink>
📝 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
<img
src="/sushi-main-logo.png"
alt=""
class="mx-auto h-20 w-auto motion-preset-pop"
>
<NuxtLink to="/" aria-label="Home">
<img
src="/sushi-main-logo.png"
alt="Webinar logo"
class="mx-auto h-20 w-auto motion-preset-pop"
>
</NuxtLink>
🤖 Prompt for AI Agents
In apps/webinar/app/components/HeaderLogo.vue around lines 2 to 6, the <img>
currently has an empty alt and is not a link; replace it with a NuxtLink to the
site root that wraps the image, and give the image a meaningful alt that
describes the logo and destination (e.g., alt="Sushi — Home"); preserve existing
classes and motion presets, do not set aria-hidden on the image, and ensure the
NuxtLink points to "/" so clicking the logo returns users to the homepage.

Comment on lines +3 to +7
<h1 class="text-4xl font-bold mb-4">
{{ $t('error.title') }} {{ error?.statusCode }}
</h1>
<p>{{ error?.statusMessage }}</p>

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

Missing i18n keys; add safe fallback for statusMessage

error.title and common.to-home aren’t defined in ru-RU.json. Also guard empty statusMessage.

-    <h1 class="text-4xl font-bold mb-4">
-      {{ $t('error.title') }} {{ error?.statusCode }}
-    </h1>
-    <p>{{ error?.statusMessage }}</p>
+    <h1 class="text-4xl font-bold mb-4">
+      {{ $t('error.title') }} {{ error?.statusCode }}
+    </h1>
+    <p v-if="error?.statusMessage">{{ error.statusMessage }}</p>
+    <p v-else>{{ $t('error.common') }}</p>
📝 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
<h1 class="text-4xl font-bold mb-4">
{{ $t('error.title') }} {{ error?.statusCode }}
</h1>
<p>{{ error?.statusMessage }}</p>
<h1 class="text-4xl font-bold mb-4">
{{ $t('error.title') }} {{ error?.statusCode }}
</h1>
<p v-if="error?.statusMessage">{{ error.statusMessage }}</p>
<p v-else>{{ $t('error.common') }}</p>
🤖 Prompt for AI Agents
In apps/webinar/app/error.vue around lines 3 to 7, the template uses i18n keys
that are missing in ru-RU.json and directly renders error.statusMessage without
a guard; add the missing keys (error.title and common.to-home) to ru-RU.json,
and update the template to use translation fallbacks for error.title and
common.to-home and guard/display a safe default for statusMessage when empty
(e.g., show a translated 'unknown error' or a static fallback string).

Comment thread apps/webinar/app/error.vue
Comment on lines +50 to +60
const links = ref([
{
label: 'Записаться',
to: 'https://t.me/SLFranchiseBot',
target: '_blank',
trailingIcon: 'i-lucide-arrow-right',
ui: {
base: 'px-6 text-xl',
},
},
])
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

External link opens in new tab without rel=noopener — fix reverse tabnabbing and simplify reactivity.

Add rel to external _blank link and avoid an unnecessary ref here.

Apply this diff:

-const links = ref([
+const links = [
   {
     label: 'Записаться',
     to: 'https://t.me/SLFranchiseBot',
     target: '_blank',
+    rel: 'noopener noreferrer',
     trailingIcon: 'i-lucide-arrow-right',
     ui: {
       base: 'px-6 text-xl',
     },
   },
-])
+]
📝 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 links = ref([
{
label: 'Записаться',
to: 'https://t.me/SLFranchiseBot',
target: '_blank',
trailingIcon: 'i-lucide-arrow-right',
ui: {
base: 'px-6 text-xl',
},
},
])
const links = [
{
label: 'Записаться',
to: 'https://t.me/SLFranchiseBot',
target: '_blank',
rel: 'noopener noreferrer',
trailingIcon: 'i-lucide-arrow-right',
ui: {
base: 'px-6 text-xl',
},
},
];
🤖 Prompt for AI Agents
In apps/webinar/app/pages/index.vue around lines 50 to 60, the external link
opens with target: '_blank' but lacks rel: 'noopener' (risk of reverse
tabnabbing) and the links array is unnecessarily wrapped in a ref; change the
links declaration from a ref to a plain constant (no reactive ref) and add rel:
'noopener' to the link object when target is '_blank' so the entry becomes a
plain array with the same fields plus rel: 'noopener'.

Comment on lines +18 to +25
"error": {
"common": "Неверное значение",
"not-selected": "Не выбрано",
"length": {
"invalid": "Недопустимое количество символов"
},
"file-size-or-type": "Недопустимый размер или тип файла"
}
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

Fix typos/diacritics; add missing keys (error.title, common.to-home)

Current text uses decomposed “й” (“й”) and is misspelled; also missing required keys referenced in templates.

   "error": {
+    "title": "Ошибка",
     "common": "Неверное значение",
     "not-selected": "Не выбрано",
     "length": {
       "invalid": "Недопустимое количество символов"
     },
-    "file-size-or-type": "Недопустимый размер или тип файла"
+    "file-size-or-type": "Недопустимый размер или тип файла"
   }

Also add a button label:

   "common": {
     "color-mode": {
       "title": "Тема",
       "select": "Выберите тему интерфейса",
       "system": "Системная",
       "light": "Светлая",
       "dark": "Темная",
       "switch": {
         "system": "Переключиться на системную тему",
         "light": "Переключиться на светлый режим",
         "dark": "Переключиться на темный режим"
       }
-    }
+    },
+    "to-home": "На главную"
   },
📝 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
"error": {
"common": "Неверное значение",
"not-selected": "Не выбрано",
"length": {
"invalid": "Недопустимое количество символов"
},
"file-size-or-type": "Недопустимый размер или тип файла"
}
"error": {
"title": "Ошибка",
"common": "Неверное значение",
"not-selected": "Не выбрано",
"length": {
"invalid": "Недопустимое количество символов"
},
"file-size-or-type": "Недопустимый размер или тип файла"
},
"common": {
"color-mode": {
"title": "Тема",
"select": "Выберите тему интерфейса",
"system": "Системная",
"light": "Светлая",
"dark": "Темная",
"switch": {
"system": "Переключиться на системную тему",
"light": "Переключиться на светлый режим",
"dark": "Переключиться на тёмный режим"
}
},
"to-home": "На главную"
},
🤖 Prompt for AI Agents
In apps/webinar/i18n/locales/ru-RU.json around lines 18 to 25, fix the
misspelled/decomposed character in "Недопустимый размер или тип файла" by
replacing it with "Недопустимый размер или тип файла", and add the missing
localization keys referenced by templates: "error.title" (a short title for
error dialogs) and "common.to-home" (button label to return home); ensure keys
are placed at the correct JSON nesting and use consistent Russian text and
proper Unicode composition for all characters.

Comment on lines +15 to +36
fonts: {
defaults: {
weights: [400, 500, 600, 700, 800, 900],
styles: ['normal', 'italic'],
subsets: [
'cyrillic-ext',
'cyrillic',
'latin-ext',
'latin',
],
},
families: [
{
name: 'Nunito',
provider: 'google',
},
{
name: 'Nunito Sans',
provider: 'google',
},
],
},
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 fonts module availability.

The fonts block requires a fonts integration (often @nuxt/fonts) either directly or via @roll-stack/ui. Confirm it’s wired; otherwise this block is no-op.


🏁 Script executed:

#!/bin/bash
rg -nP '@nuxt/fonts|nuxt-fonts|@nuxtjs/fontaine' **/nuxt.config.{ts,js} -C2 || true
rg -nP '@nuxt/fonts|nuxt-fonts' --glob package.json -C2 || true

Length of output: 115


🏁 Script executed:

#!/bin/bash
set -e
# Locate nuxt.config.ts
find . -type f -name 'nuxt.config.ts' -print

# Show first 50 lines of nuxt.config.ts
sed -n '1,50p' apps/webinar/nuxt.config.ts || true

# Check for fonts integration in nuxt.config.ts
rg -nP '@nuxt/fonts|@nuxtjs/fontaine|nuxt-fonts' -C2 apps/webinar/nuxt.config.ts || true

# Check for @roll-stack/ui usage in nuxt.config.ts
rg -nP '@roll-stack/ui' -C2 apps/webinar/nuxt.config.ts || true

# Check for fonts or @roll-stack/ui in any package.json
rg -nP '"(@nuxt/fonts|@roll-stack/ui|nuxt-fonts|@nuxtjs/fontaine)"' --glob 'package.json' -C2 || true

Length of output: 3725


🏁 Script executed:

#!/bin/bash
set -e

# Show the UI package nuxt.config.ts
echo "=== packages/ui/nuxt.config.ts (first 200 lines) ==="
sed -n '1,200p' packages/ui/nuxt.config.ts || true

# Search for fonts integration in UI package config
echo "=== fonts-related patterns in packages/ui/nuxt.config.ts ==="
rg -nP '@nuxt/fonts|@nuxtjs/fontaine|nuxt-fonts' -C2 packages/ui/nuxt.config.ts || true

# Search for modules array in UI package config to see if fonts is included
echo "=== modules array in packages/ui/nuxt.config.ts ==="
rg -nP 'modules\s*:\s*\[' -C5 packages/ui/nuxt.config.ts || true

Length of output: 1473


Register a fonts integration module
The fonts block in apps/webinar/nuxt.config.ts is unused: no @nuxt/fonts, nuxt-fonts or @nuxtjs/fontaine is installed or registered (neither here nor via @roll-stack/ui), so the configuration is a no-op. Add a fonts module to modules or wire it into @roll-stack/ui.

🤖 Prompt for AI Agents
In apps/webinar/nuxt.config.ts around lines 15 to 36 the fonts configuration is
dead (no fonts integration module registered), so either register a compatible
fonts module or remove/relocate the block: install and add a fonts module (e.g.,
@nuxtjs/fontaine or @nuxt/fonts compatible with your Nuxt version) to the
modules array in this config (or wire the module inside @roll-stack/ui if that
package should own global integrations), then ensure the chosen module supports
the provider keys used (google) and keep the weights/styles/subsets as-is;
alternatively delete the unused fonts block to avoid no-op config.

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
5.0% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@hmbanan666 hmbanan666 merged commit 1b7573b into main Sep 25, 2025
6 of 8 checks passed
@hmbanan666 hmbanan666 deleted the webinar branch September 25, 2025 13:21
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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/database/src/tables.ts (2)

600-601: Store bot tokens encrypted or in a secret manager, not as plaintext.

telegram_bots.token is sensitive; keep out of logs and encrypt at rest (envelope + KMS) or store externally (e.g., cloud secrets).

Example: encrypt/decrypt at the service layer; restrict selection columns to avoid accidental exposure.


720-721: Locker passwords must not be stored in plaintext.

Encrypt at rest (or design for zero-knowledge if feasible). At minimum, AES-GCM with per-record random IV and KMS-managed key; never log this field.

If display to users is required, use reversible encryption; if only verify, store hashed variants with strong KDF (Argon2id).

packages/queue/src/connection.ts (1)

48-50: Re-run topology declarations on reconnect
Connection.exchangeDeclare/queueDeclare/queueBind wait for an open channel but run only once—they aren’t auto-recovered on reconnect. Inside createConnection (packages/queue/src/connection.ts), hook your declareExchanges()/declareQueuesAndBindings() calls to the ‘connection’ event (or switch to createConsumer/createPublisher for automatic recovery):

let setupInProgress = false
rabbit.on('connection', () => {
  if (setupInProgress) return
  setupInProgress = true
  void declareExchanges()
    .then(() => declareQueuesAndBindings())
    .finally(() => { setupInProgress = false })
})
🧹 Nitpick comments (5)
packages/database/src/tables.ts (1)

376-376: Timezone: prefer IANA tz IDs over fixed numeric offsets.

Offsets miss DST and negative zones; IANA IDs (e.g., Europe/Moscow) are safer. If offsets are required, extend unions to include negatives.

Example change (high-level):

  • Replace entities.TimeZone with a string type for IANA IDs (or a validated list).
  • Migrate existing data if any (+00:00 → Etc/UTC).
packages/database/src/types/entities.ts (2)

20-33: Timezone union is limited and not DST-safe; switch to IANA IDs or extend to negatives.

Consider replacing the offset union with IANA tz strings (preferred), or include negative offsets if offsets are mandatory.

If moving to IANA:

  • export type TimeZone = string
  • Validate on write using a known tz list in the service layer.

36-47: Export const arrays and derive types to avoid duplication and enable DB checks.

Having only type unions gives no runtime list. Add const arrays (as const) and define types from them. Reuse arrays for pgEnum/CHECK constraints to keep single source of truth.

Illustration for one group (apply pattern to others):

export const PaymentMethodTypes = ['card', 'cash', 'online'] as const
export type PaymentMethodType = typeof PaymentMethodTypes[number]

Then in tables.ts use PaymentMethodTypes for pgEnum/CHECK.

Also applies to: 51-56, 57-66, 67-69, 70-73, 74-75, 76-77, 78-81, 82-91, 92-96

packages/queue/src/connection.ts (2)

15-24: Use config.exchange (not the object key) and reuse the connection.
Avoid relying on key===value invariants; read the explicit exchange field and avoid repeated useConnection() calls.

-async function declareExchanges() {
-  for (const [name, config] of Object.entries(EXCHANGES)) {
-    await useConnection().exchangeDeclare({
-      exchange: name,
-      type: config.type,
-      autoDelete: config.autoDelete,
-      durable: config.durable,
-    })
-  }
-}
+async function declareExchanges() {
+  const conn = useConnection()
+  for (const config of Object.values(EXCHANGES)) {
+    await conn.exchangeDeclare({
+      exchange: config.exchange,
+      type: config.type,
+      autoDelete: config.autoDelete,
+      durable: config.durable,
+    })
+  }
+}

26-44: Same: use config.queue and reuse the connection (minor readability/perf).
This removes key coupling and repeated lookups.

-async function declareQueuesAndBindings() {
-  for (const [queue, config] of Object.entries(QUEUES)) {
-    await useConnection().queueDeclare({
-      queue,
-      arguments: config.arguments,
-      autoDelete: config.autoDelete,
-      durable: config.durable,
-    })
-  }
-
-  for (const { exchange, queue, routingKey } of BINDINGS) {
-    await useConnection().queueBind({
-      exchange,
-      queue,
-      routingKey,
-    })
-  }
-}
+async function declareQueuesAndBindings() {
+  const conn = useConnection()
+  for (const config of Object.values(QUEUES)) {
+    await conn.queueDeclare({
+      queue: config.queue,
+      arguments: config.arguments,
+      autoDelete: config.autoDelete,
+      durable: config.durable,
+    })
+  }
+
+  for (const { exchange, queue, routingKey } of BINDINGS) {
+    await conn.queueBind({ exchange, queue, routingKey })
+  }
+}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cbc6094 and d8814e1.

📒 Files selected for processing (11)
  • .vscode/settings.json (1 hunks)
  • apps/webinar/app/app.vue (1 hunks)
  • apps/webinar/i18n/locales/ru-RU.json (1 hunks)
  • apps/webinar/nuxt.config.ts (1 hunks)
  • packages/database/src/tables.ts (19 hunks)
  • packages/database/src/types/entities.ts (1 hunks)
  • packages/database/src/types/index.ts (1 hunks)
  • packages/database/src/types/tables.ts (1 hunks)
  • packages/queue/src/connection.ts (2 hunks)
  • packages/queue/src/exchanges.ts (0 hunks)
  • packages/queue/src/queues.ts (0 hunks)
💤 Files with no reviewable changes (2)
  • packages/queue/src/exchanges.ts
  • packages/queue/src/queues.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/database/src/types/index.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/webinar/app/app.vue
  • apps/webinar/nuxt.config.ts
  • apps/webinar/i18n/locales/ru-RU.json
🧰 Additional context used
🧬 Code graph analysis (2)
packages/queue/src/connection.ts (2)
packages/queue/src/exchanges.ts (1)
  • EXCHANGES (3-22)
packages/queue/src/queues.ts (2)
  • QUEUES (4-25)
  • BINDINGS (33-49)
packages/database/src/tables.ts (1)
packages/database/src/types/entities.ts (24)
  • UserType (5-13)
  • UserGender (14-14)
  • PermissionCode (36-46)
  • NotificationOption (1-3)
  • AgreementPatentStatus (16-16)
  • WeightUnit (18-18)
  • MediaFormat (48-48)
  • ResolutionType (55-55)
  • NotificationType (51-53)
  • CheckoutStatus (57-64)
  • CheckoutDeliveryMethod (65-65)
  • TimeZone (20-32)
  • ChannelType (70-70)
  • PaymentMethodType (72-72)
  • PostStatus (68-68)
  • PostType (67-67)
  • FileFormat (49-49)
  • FeedbackPointType (74-74)
  • TelegramUserType (76-76)
  • TicketStatus (78-78)
  • TicketFileType (80-80)
  • CommunicationChannel (82-90)
  • ActivityScheduleTag (92-95)
  • FlowItemType (34-34)
⏰ 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 (6)
.vscode/settings.json (1)

72-73: Enabling circular-import detection looks good.

The added workspace setting helps catch cyclical dependencies without impacting other tooling and the JSON remains valid.

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

29-30: Arrays in JSONB default to []: good choice.

This avoids null checks in reads and keeps types consistent with PermissionCode[]/NotificationOption[].

packages/database/src/types/entities.ts (2)

48-50: MediaFormat/FileFormat separation looks intentional; LGTM.

Clear separation between binary file types and media items.


1-4: NotificationOption set looks minimal—confirm coverage.

If more sources are expected soon, consider the const-array pattern to ease evolution and DB constraints later.

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

2-2: Import via namespace & type inference LGTM
Deriving public types from table definitions avoids drift. Confirm whether pivot-table types (channelKitchens, paymentMethodsOnKitchens) are intentionally private by checking for external usage:

rg -n -C2 '\b(channelKitchens|paymentMethodsOnKitchens)\b' | grep -v '^packages/database/'
packages/queue/src/connection.ts (1)

3-4: Good: decoupled from connection; no more circular import risk.
Importing static topology (EXCHANGES, QUEUES, BINDINGS) here removes the prior connection import cycle. Looks clean.

Comment on lines +19 to 22
type: varchar('type').notNull().$type<entities.UserType>(),
isActive: boolean('is_active').notNull().default(true),
gender: varchar('gender').notNull().default('unknown').$type<UserGender>(),
gender: varchar('gender').notNull().default('unknown').$type<entities.UserGender>(),
name: varchar('name').notNull(),
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 | 🟠 Major

Enforce enum fields at the database level (pgEnum or CHECK) instead of only TS unions.

$type<> gives compile-time safety but doesn’t constrain stored values. Add pgEnum or CHECK constraints for these columns to protect data integrity.

Example (posts.status/type) using pgEnum; adjust similarly for other enum-like fields:

- import { boolean, date, integer, jsonb, numeric, pgTable, text, timestamp, varchar } from 'drizzle-orm/pg-core'
+ import { boolean, date, integer, jsonb, numeric, pgTable, text, timestamp, varchar, pgEnum } from 'drizzle-orm/pg-core'

+const postStatusEnum = pgEnum('post_status', ['draft', 'scheduled', 'published'])
+const postTypeEnum = pgEnum('post_type', ['telegram', 'vk'])

 export const posts = pgTable('posts', {
   ...
-  status: varchar('status').notNull().$type<entities.PostStatus>(),
-  type: varchar('type').notNull().$type<entities.PostType>(),
+  status: postStatusEnum('status').notNull(),
+  type: postTypeEnum('type').notNull(),
   ...
 })

If you prefer CHECK constraints (to avoid pg enums), consider exporting const arrays from entities (as runtime values) and reuse them to avoid duplication.

Also applies to: 29-30, 69-69, 162-163, 258-258, 273-273, 305-305, 334-335, 376-376, 409-409, 426-426, 453-455, 498-498, 533-533, 609-609, 634-634, 649-649, 678-679, 747-747

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