Skip to content

feat: essence init#378

Merged
hmbanan666 merged 4 commits into
mainfrom
essence
Apr 19, 2025
Merged

feat: essence init#378
hmbanan666 merged 4 commits into
mainfrom
essence

Conversation

@hmbanan666
Copy link
Copy Markdown
Contributor

@hmbanan666 hmbanan666 commented Apr 18, 2025

Summary by CodeRabbit

  • New Features

    • Introduced the "Essence" application with a Nuxt-based frontend supporting English and Russian locales.
    • Added main layout, error page, and custom UI styling including gradient button and tab variants.
    • Implemented product management APIs for creating, updating, and retrieving products.
    • Defined database schemas and repository for products, variants, and media with typed models.
    • Added validation schemas for product creation and updates.
    • Integrated internationalization and locale-aware document attributes.
  • Chores

    • Added Dockerfile and Docker Compose setup for containerized deployment.
    • Included health check endpoint and script for service monitoring.
    • Configured workspace and CI/CD pipelines to support the new app.
    • Established database connection initialization and migration tooling.
  • Documentation

    • Added example environment configuration and TypeScript global types.
    • Included localization files for English and Russian languages.
    • Provided app configuration and Drizzle ORM migration settings.

@hmbanan666 hmbanan666 self-assigned this Apr 18, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 18, 2025

Walkthrough

This update introduces the foundational structure for a new application named "essence" within a monorepo. It adds configuration files for Nuxt, TypeScript, Docker, and Drizzle ORM, as well as environment variable examples and package definitions. The server-side implements database connection logic, schema definitions, repositories, API endpoints for product management, error handling utilities, and localization support. The client-side includes base Vue components, styling, and internationalization files. Additionally, the update expands CI/CD workflows and workspace dependency catalogs to include the new application and its dependencies.

Changes

File(s) / Path(s) Change Summary
.github/workflows/docker-nightly.yml, .github/workflows/docker-release.yml Updated CI workflows to include "essence" in the list of recognized applications/packages for change detection and release.
pnpm-workspace.yaml Added Drizzle ORM and PostgreSQL-related dependencies to the workspace catalog.
docker/essence/Dockerfile, docker/essence/health-check.sh, docker/essence/docker-compose.yaml Introduced Dockerfile for multi-stage build and runtime, health check script, and Docker Compose configuration for a PostgreSQL database service.
apps/essence/.env.example Added example environment configuration file with placeholder variables for session password, database URL, and version.
apps/essence/package.json Defined package metadata, scripts, dependencies, and devDependencies for the @nextorders/essence app.
apps/essence/tsconfig.json, apps/essence/server/tsconfig.json Added TypeScript configuration files extending Nuxt base configs.
apps/essence/app/app.vue, apps/essence/app/error.vue Introduced main application and error Vue components with internationalization and layout logic.
apps/essence/app/app.config.ts Added UI styling variants for buttons and tabs in the app configuration.
apps/essence/app/assets/css/styles.css Applied a serif font to all heading elements using a CSS variable.
apps/essence/i18n/locales/en-US.json, apps/essence/i18n/locales/ru-RU.json Added localization metadata files for English and Russian.
apps/essence/nuxt.config.ts Added Nuxt configuration: extends UI package, sets up modules, i18n, Pinia, CSS, and compatibility settings.
apps/essence/drizzle.config.ts Introduced Drizzle ORM configuration for schema and migrations.
apps/essence/types/index.d.ts Defined global types for locale, locale-value pairs, and weight units.
apps/essence/shared/services/locale.ts Exported a Zod enum schema for supported locales.
apps/essence/shared/services/product.ts Defined and exported Zod validation schemas and types for product creation and update.
apps/essence/server/services/db/connection.ts Implemented database connection logic using Drizzle ORM and PostgreSQL.
apps/essence/server/services/db/database.ts Provided singleton database management and migration functions.
apps/essence/server/services/db/tables.ts Defined PostgreSQL table schemas for products, variants, and media, including relations.
apps/essence/server/services/db/types.ts Added type aliases for select and insert models of products, variants, and media, and re-exported the Database type.
apps/essence/server/services/db/repository/product.ts Implemented static repository class for product CRUD operations.
apps/essence/server/services/db/repository/index.ts Added a repository class exposing product operations and a health check method.
apps/essence/server/plugins/01.database.ts Created Nitro plugin to initialize the database connection at server startup using environment variables.
apps/essence/server/api/health/index.get.ts Added health check API endpoint using the repository's health method and error resolver.
apps/essence/server/api/product/[productId]/index.get.ts Added GET API endpoint to fetch a product by ID with error handling.
apps/essence/server/api/product/[productId]/index.patch.ts Added PATCH API endpoint to update a product by ID with validation, localization, and error handling.
apps/essence/server/api/product/index.post.ts Added POST API endpoint for product creation with validation and error handling.
apps/essence/server/utils/error.ts Introduced centralized error resolver function for consistent error responses.
apps/essence/server/utils/locale.ts Added utility to update locale-value arrays for localization.
apps/essence/server/utils/logger.ts Provided a logger utility function supporting optional tagging.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API
    participant Repository
    participant Database

    Client->>API: POST /api/product (create product)
    API->>Repository: Product.create(data)
    Repository->>Database: Insert product
    Database-->>Repository: Product record
    Repository-->>API: Product
    API-->>Client: Product

    Client->>API: GET /api/product/:id
    API->>Repository: Product.find(id)
    Repository->>Database: Query by id
    Database-->>Repository: Product record or null
    Repository-->>API: Product or null
    API-->>Client: Product or error

    Client->>API: PATCH /api/product/:id (update product)
    API->>Repository: Product.find(id)
    Repository->>Database: Query by id
    Database-->>Repository: Product or null
    Repository-->>API: Product or null
    API->>Repository: Product.update(id, data)
    Repository->>Database: Update product
    Database-->>Repository: Updated product
    Repository-->>API: Product
    API-->>Client: { ok: true }

    Client->>API: GET /api/health
    API->>Repository: checkHealth()
    Repository->>Database: Simple query
    Database-->>Repository: Success/Fail
    Repository-->>API: true/false
    API-->>Client: { ok: true } or error
Loading

Poem

🐰
A new app called Essence, so fresh and bright,
With Docker and Drizzle, it takes flight.
Products and variants, all typed with care,
Locales and schemas, everywhere!
Health checks and errors, all handled with grace,
Now Essence is ready to take its place.
The rabbit is happy—let’s hop to the next race! 🚀

Tip

⚡💬 Agentic Chat (Pro Plan, General Availability)
  • We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 203b27c and d2f7bd1.

📒 Files selected for processing (1)
  • apps/essence/shared/services/product.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/essence/shared/services/product.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

🧹 Nitpick comments (14)
apps/essence/app/assets/css/styles.css (1)

1-3: Add fallback serif font for failover

Include a generic serif fallback in case --font-serif isn’t defined:

@@ apps/essence/app/assets/css/styles.css
-h1, h2, h3, h4, h5, h6 {
-  font-family: var(--font-serif);
-}
+h1, h2, h3, h4, h5, h6 {
+  font-family: var(--font-serif), serif;
+}

This ensures consistent typography even if the custom variable is missing.

pnpm-workspace.yaml (1)

45-45: Review pg version against your database

pg@^8.14.1 should be compatible with your PostgreSQL server version. Consider adding a CI check matrix for different PG versions if you need broader support.

apps/essence/.env.example (1)

1-9: Enhance .env.example with guidance

Add instructions and examples to help developers populate this file and secure secrets. For example:

# Copy to .env and update:
# - NUXT_SESSION_PASSWORD: A strong, random string (e.g., at least 32 characters)
# - NUXT_DATABASE_URL: Format -> postgres://USER:PASSWORD@HOST:PORT/DATABASE
# - VERSION: Application version tag, e.g., "1.0.0"
# Ensure .env is listed in .gitignore to avoid committing secrets.
docker/essence/health-check.sh (1)

1-2: Consider hardening the health-check script
To avoid hangs and reduce noise, you could:

  • Add set -e at the top to exit on any failure.
  • Use curl -sf to silence successful output and still fail on HTTP errors.

Example diff:

 #!/bin/sh
+set -e
-curl -f http://localhost:3000/api/health || exit 1
+curl -sf --connect-timeout 5 http://localhost:3000/api/health
apps/essence/app/error.vue (1)

1-17: Error template looks good but consider adding fallback for missing error details

The error component has a clean layout with proper internationalization support. The error display shows both status code and message when available.

Consider adding nullish coalescing operators (??) for error properties to handle cases where they might be undefined:

-      {{ $t('error.title') }} {{ error?.statusCode }}
+      {{ $t('error.title') }} {{ error?.statusCode ?? '' }}
-    <p>{{ error?.statusMessage }}</p>
+    <p>{{ error?.statusMessage ?? '' }}</p>
apps/essence/server/services/db/connection.ts (1)

8-15: Consider adding error handling and pool configuration options

The database connection setup looks good but could benefit from additional error handling and connection pool configuration.

Consider improving the connection function with error handling and pool configuration:

 export function createConnection(connectionString: string): Database {
-  const pool = new pg.Pool({ connectionString })
+  const pool = new pg.Pool({
+    connectionString,
+    max: 20, // Maximum number of clients in the pool
+    idleTimeoutMillis: 30000, // How long a client is allowed to remain idle before being closed
+    connectionTimeoutMillis: 2000, // How long to wait for a connection
+  })
+
+  // Add error listener to handle connection issues
+  pool.on('error', (err) => {
+    console.error('Unexpected error on idle client', err)
+  })

   return drizzle({
     client: pool,
     schema: tables,
   })
 }
apps/essence/server/services/db/tables.ts (1)

1-8: Solid foundation for the products table schema.

The table schema is well-structured with appropriate cuid2 primary key and timestamp fields. Using snake_case for database columns while maintaining camelCase in TypeScript is a good practice.

Consider the following enhancements:

  1. Add a mechanism to automatically update the updatedAt field when records are modified. This could be handled at the query level or through triggers.

  2. The table currently only has identity and audit fields. You'll likely need to add product-specific fields (name, description, price, etc.) as the application develops.

  3. If you'll be querying frequently by timestamps, consider adding indexes to the timestamp fields:

export const products = pgTable('products', {
  id: cuid2('id').defaultRandom().primaryKey(),
-  createdAt: timestamp('created_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
-  updatedAt: timestamp('updated_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
+  createdAt: timestamp('created_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
+  updatedAt: timestamp('updated_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
}, (table) => {
+  return {
+    createdAtIdx: index('created_at_idx').on(table.createdAt),
+    updatedAtIdx: index('updated_at_idx').on(table.updatedAt),
+  }
})
apps/essence/nuxt.config.ts (1)

1-22: Good configuration setup for the Nuxt application.

The Nuxt configuration is well-structured with appropriate modules and settings for the essence app. I particularly like the i18n setup with English and Russian locales.

You might want to consider a few enhancements:

  1. Add TypeScript type checking for runtime config if you plan to use environment variables:
export default defineNuxtConfig({
  extends: ['@nextorders/ui'],
  modules: ['@pinia/nuxt', 'nuxt-auth-utils'],
+ runtimeConfig: {
+   // Server-side environment variables
+   databaseUrl: '',
+   // Public variables exposed to the client
+   public: {
+     apiBaseUrl: '',
+   }
+ },
  future: {
    compatibilityVersion: 4,
  },
  // remaining configuration...
})
  1. Consider adding auto-import configuration for the components to improve developer experience:
export default defineNuxtConfig({
  // existing configuration...
+ components: {
+   dirs: [
+     '~/components'
+   ]
+ },
  // remaining configuration...
})
  1. Consider configuring the Nitro server explicitly for better control over the server behavior:
export default defineNuxtConfig({
  // existing configuration...
+ nitro: {
+   compressPublicAssets: true,
+ },
  // remaining configuration...
})
apps/essence/server/services/db/database.ts (2)

12-20: Well-implemented database migration function.

The migration implementation correctly handles path resolution and error checking before attempting to run migrations.

Consider adding logging for better observability during migrations:

export async function useMigrateDatabase(migrationFolder: string) {
  if (!instance) {
    throw new Error('Database is not created')
  }

+  console.log(`Running migrations from ${resolve(migrationFolder)}`)
  await migrate(instance, {
    migrationsFolder: resolve(migrationFolder),
  })
+  console.log('Migrations completed successfully')
}

22-28: Clean accessor for database instance.

The accessor function properly validates instance existence before returning it, which prevents potential null reference errors.

For clarity in error messages, you might want to be more specific about the expected initialization sequence:

export function useDatabase(): Database {
  if (!instance) {
-    throw new Error('Database is not created')
+    throw new Error('Database is not initialized. Call useCreateDatabase() first.')
  }

  return instance
}
apps/essence/app/app.vue (1)

19-20: Guard against missing locale entries
Accessing locales[locale.value] directly may throw if the key is undefined. Consider a fallback to default locale:

const current = locales[locale.value] ?? locales['en']
const lang = computed(() => current.code)
const dir = computed(() => current.dir)
docker/essence/Dockerfile (3)

9-11: Pin pnpm version for reproducible builds
Installing pnpm without a version may introduce breaking changes when the latest is released. Pin to a known version:

RUN npm install --ignore-scripts -g pnpm@8.6.7 && \
    pnpm i --frozen-lockfile && \
    pnpm build --filter @nextorders/essence

24-27: Ensure proper file permissions for non‑root user
After copying artifacts from the builder, /app is still owned by root. If your app writes to disk (logs, cache), appuser may lack permissions. Consider:

RUN chown -R appuser:appgroup /app && \
    chmod +x ./health-check.sh

46-47: Simplify ENTRYPOINT and CMD
You can merge these for clarity and avoid two separate directives:

- ENTRYPOINT ["node"]
- CMD ["/app/server/index.mjs"]
+ ENTRYPOINT ["node", "/app/server/index.mjs"]
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d6a1ba and df81d08.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (20)
  • apps/essence/.env.example (1 hunks)
  • apps/essence/app/app.config.ts (1 hunks)
  • apps/essence/app/app.vue (1 hunks)
  • apps/essence/app/assets/css/styles.css (1 hunks)
  • apps/essence/app/error.vue (1 hunks)
  • apps/essence/i18n/locales/en-US.json (1 hunks)
  • apps/essence/i18n/locales/ru-RU.json (1 hunks)
  • apps/essence/nuxt.config.ts (1 hunks)
  • apps/essence/package.json (1 hunks)
  • apps/essence/server/api/health/index.get.ts (1 hunks)
  • apps/essence/server/services/db/connection.ts (1 hunks)
  • apps/essence/server/services/db/database.ts (1 hunks)
  • apps/essence/server/services/db/drizzle.config.ts (1 hunks)
  • apps/essence/server/services/db/tables.ts (1 hunks)
  • apps/essence/server/services/db/types.ts (1 hunks)
  • apps/essence/server/tsconfig.json (1 hunks)
  • apps/essence/tsconfig.json (1 hunks)
  • docker/essence/Dockerfile (1 hunks)
  • docker/essence/health-check.sh (1 hunks)
  • pnpm-workspace.yaml (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/essence/server/services/db/database.ts (1)
apps/essence/server/services/db/connection.ts (2)
  • Database (6-6)
  • createConnection (8-15)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (13)
pnpm-workspace.yaml (2)

24-24: Confirm compatibility of @types/pg

You’ve pinned @types/pg to ^8.11.13. Double-check that it aligns with your pg client (^8.14.1) to prevent mismatched type definitions.


33-35: Approve Drizzle ORM dependencies

drizzle-cuid2, drizzle-kit, and drizzle-orm are essential for your database layer. Versions look consistent; ensure they’re tested together in your CI workflows.

apps/essence/i18n/locales/en-US.json (1)

1-4: Locale file structure looks good

The English locale defines name and short-name correctly. Just confirm consistency with ru-RU.json and your Nuxt i18n config regarding key naming (e.g., kebab-case vs camelCase).

apps/essence/server/tsconfig.json (1)

1-3: Verify the extended TypeScript config exists

You’re extending ../.nuxt/tsconfig.server.json. Ensure this file is generated by Nuxt and includes the necessary compiler options for server-side code.

apps/essence/i18n/locales/ru-RU.json (1)

1-4: Locale file looks good and valid
The JSON defines the Russian locale correctly and matches the naming convention used for other locales.

apps/essence/tsconfig.json (1)

1-3: Minimal TS config extension is correct
Extending the Nuxt-generated tsconfig.json without overrides aligns with the expected project setup.

apps/essence/server/api/health/index.get.ts (1)

1-5: Health endpoint implementation is straightforward
The handler always returns { ok: true }, which suffices for a basic readiness probe.

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

1-22: LGTM! Well-structured UI configuration

The app config nicely defines gradient variants for buttons and tabs using appropriate styling properties and states. The configuration properly handles different states (hover, disabled, focus) and uses CSS custom properties for theming flexibility.

apps/essence/app/error.vue (1)

19-28: LGTM! Clear error handling implementation

The script setup is properly typed and the error handling function is concise and effective, redirecting to the home page upon error clearing.

apps/essence/package.json (1)

1-29: LGTM! Well-configured package setup

The package.json is properly configured with necessary scripts and dependencies for a Nuxt application with Drizzle ORM and PostgreSQL integration. The port assignments are consistent across scripts, and the dependencies align with the project's needs.

apps/essence/server/services/db/types.ts (1)

1-7: Clean type definitions for database models.

The type definitions are concise and follow good practices by leveraging Drizzle ORM's type inference capabilities. This approach ensures type safety and consistency between your database schema and application code.

The use of InferSelectModel and InferInsertModel is perfect for maintaining type synchronization with the database schema changes.

apps/essence/server/services/db/database.ts (1)

6-10: Good use of singleton pattern for database connection.

The singleton implementation for the database connection is clean and follows good practices. This ensures a single connection pool is maintained throughout the application.

apps/essence/app/app.vue (1)

14-22: Confirm auto-imports for computed, useI18n, and useHead
Nuxt 3 supports auto‑importing many composables, but if your config doesn’t include these, you’ll get runtime ReferenceError. Make sure they’re available or import them explicitly:

import { computed } from 'vue'
import { useI18n } from 'vue-i18n' // or from '#imports'
import { useHead } from '@vueuse/head' // or from '#imports'

Comment on lines +9 to +12
dbCredentials: {
url: process.env.DATABASE_URL!,
},
})
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

Ensure DATABASE_URL is defined at startup
Relying on process.env.DATABASE_URL! will throw an unclear runtime error if the variable is missing. It's safer to validate explicitly and fail fast with a descriptive message.

Example diff:

 import 'dotenv/config'

-export default defineConfig({
+if (!process.env.DATABASE_URL) {
+  throw new Error('Environment variable DATABASE_URL must be set')
+}
+export default defineConfig({
   dialect: 'postgresql',
   schema: './tables.ts',
   out: './migrations',
   dbCredentials: {
-    url: process.env.DATABASE_URL!,
+    url: process.env.DATABASE_URL,
   },
 })
📝 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
dbCredentials: {
url: process.env.DATABASE_URL!,
},
})
import 'dotenv/config'
if (!process.env.DATABASE_URL) {
throw new Error('Environment variable DATABASE_URL must be set')
}
export default defineConfig({
dialect: 'postgresql',
schema: './tables.ts',
out: './migrations',
dbCredentials: {
url: process.env.DATABASE_URL,
},
})

Comment thread apps/essence/app/app.vue
Comment on lines +1 to +11
<template>
<UApp
:locale="locales[locale]"
:tooltip="{ delayDuration: 0 }"
class="min-h-dvh"
>
<NuxtLoadingIndicator :color="false" class="bg-(--ui-primary) h-[2px]" />
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</UApp>
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

Potential Tailwind class syntax error on loading indicator
The class="bg-(--ui-primary) h-[2px]" appears to use non‑standard Tailwind syntax. The correct arbitrary value syntax should be bg-[var(--ui-primary)]. Update to ensure the background color is applied:

- <NuxtLoadingIndicator :color="false" class="bg-(--ui-primary) h-[2px]" />
+ <NuxtLoadingIndicator :color="false" class="bg-[var(--ui-primary)] h-[2px]" />

Comment thread docker/essence/Dockerfile
Comment on lines +42 to +43
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s \
CMD ["health-check.sh"]
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

Fix healthcheck script invocation path
Using exec form, Docker won’t search /app by default, so ["health-check.sh"] will fail. Update to:

- HEALTHCHECK --interval=30s --timeout=5s --start-period=60s \
-     CMD ["health-check.sh"]
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=60s \
+     CMD ["./health-check.sh"]
📝 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
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s \
CMD ["health-check.sh"]
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s \
CMD ["./health-check.sh"]

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

🧹 Nitpick comments (3)
.github/workflows/docker-nightly.yml (1)

27-27: Centralize the APPS list for maintainability
As the number of applications grows, keeping the same array in multiple workflows can lead to drift. Consider extracting APPS into a reusable workflow input, an environment variable, or employing YAML anchors to DRY up your CI configuration.

apps/essence/server/plugins/01.database.ts (2)

4-6: Enhance plugin documentation.

While the comment indicates this is for database initialization, adding more details about the purpose and function of this plugin would improve maintainability.

/**
- * DB init
+ * Database initialization plugin
+ * 
+ * This plugin runs during server startup to establish a connection to the PostgreSQL database.
+ * It requires the NUXT_DATABASE_URL environment variable to be set.
+ * The plugin will throw an error if the connection cannot be established, preventing the
+ * application from starting with an invalid database configuration.
 */

1-2: Consider using a more testable approach to environment variables.

Accessing process.env directly makes the code harder to test. A more testable approach would be to use configuration services that can be mocked during testing.

You could consider creating a configuration service that provides environment variables:

// In a new file: server/services/config.ts
export function useConfig() {
  return {
    getDatabaseUrl: () => process.env.NUXT_DATABASE_URL
  }
}

// Then in this file:
import { useConfig } from '../services/config'

export default defineNitroPlugin(async () => {
  const config = useConfig()
  const databaseUrl = config.getDatabaseUrl()
  
  if (!databaseUrl) {
    throw new Error('Database URL is not defined')
  }
  
  try {
    await useCreateDatabase(databaseUrl)
    // ...
  } catch (error) {
    // ...
  }
})
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df81d08 and 587dcfb.

📒 Files selected for processing (5)
  • .github/workflows/docker-nightly.yml (1 hunks)
  • .github/workflows/docker-release.yml (1 hunks)
  • apps/essence/app/assets/css/styles.css (1 hunks)
  • apps/essence/server/plugins/01.database.ts (1 hunks)
  • apps/essence/server/services/db/drizzle.config.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/essence/app/assets/css/styles.css
  • apps/essence/server/services/db/drizzle.config.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/essence/server/plugins/01.database.ts (1)
apps/essence/server/services/db/database.ts (1)
  • useCreateDatabase (8-10)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (2)
.github/workflows/docker-nightly.yml (1)

27-27: Add “essence” to the nightly build matrix
The update correctly includes "essence" in the APPS array. Please verify that your new apps/essence directory exists and that the change‑detection logic (git diff … | grep -q "^apps/essence/") will reliably pick up file changes.

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

34-34: Support for the new “essence” package is correctly added.

The APPS array now includes "essence", ensuring the workflow will detect and process the new application alongside web-app and website.

Comment on lines +7 to +13
export default defineNitroPlugin(async () => {
if (!process.env.NUXT_DATABASE_URL) {
throw new Error('NUXT_DATABASE_URL is not defined')
}

useCreateDatabase(process.env.NUXT_DATABASE_URL)
})
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

Improve error handling and asynchronous behavior.

The plugin correctly checks for the required environment variable, but has a few areas for improvement:

  1. The useCreateDatabase function isn't awaited, but based on the context, it likely performs database operations that should be properly awaited.
  2. There's no error handling for potential exceptions that might occur during database connection.
export default defineNitroPlugin(async () => {
  if (!process.env.NUXT_DATABASE_URL) {
-    throw new Error('NUXT_DATABASE_URL is not defined')
+    throw new Error('NUXT_DATABASE_URL is not defined. Please set this environment variable before starting the application.')
  }

-  useCreateDatabase(process.env.NUXT_DATABASE_URL)
+  try {
+    await useCreateDatabase(process.env.NUXT_DATABASE_URL)
+    console.log('Database connection established successfully')
+  } catch (error) {
+    console.error('Failed to establish database connection:', error)
+    throw error
+  }
})
📝 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
export default defineNitroPlugin(async () => {
if (!process.env.NUXT_DATABASE_URL) {
throw new Error('NUXT_DATABASE_URL is not defined')
}
useCreateDatabase(process.env.NUXT_DATABASE_URL)
})
export default defineNitroPlugin(async () => {
if (!process.env.NUXT_DATABASE_URL) {
throw new Error('NUXT_DATABASE_URL is not defined. Please set this environment variable before starting the application.')
}
try {
await useCreateDatabase(process.env.NUXT_DATABASE_URL)
console.log('Database connection established successfully')
} catch (error) {
console.error('Failed to establish database connection:', error)
throw error
}
})

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

🔭 Outside diff range comments (1)
apps/essence/shared/services/product.ts (1)

56-69: 🛠️ Refactor suggestion

Improve file validation with more robust checks.

The file validation has some issues:

  1. Using z.any() loses type safety - consider using a more specific type
  2. If the file is undefined/null, the size check will fail silently
  3. The file type check could be made more robust
export const productImageUploadSchema = z.object({
-  file: z.any()
+  file: z.instanceof(File)
    .refine((file) => file?.size <= MAX_FILE_SIZE, `Max image size is 20MB.`)
    .refine(
      (file) => ACCEPTED_IMAGE_TYPES.includes(file?.type),
      'Only .jpg, .jpeg, .png and .webp formats are supported.',
    ),
})

If you're working in a Node.js environment where File is not available, consider using a more specific validation approach for your environment.

🧹 Nitpick comments (11)
apps/essence/server/utils/locale.ts (1)

1-14: Improve updateLocaleValues to avoid direct array mutation

This utility function works correctly but directly mutates the input array, which could lead to unintended side effects when the caller doesn't expect the original array to be modified.

Consider creating a new array instead of mutating the input:

export function updateLocaleValues(currentValues: LocaleValue[], value?: LocaleValue): LocaleValue[] {
  if (!value) {
-    return currentValues
+    return [...currentValues]
  }

-  const exist = currentValues.find((v) => v.locale === value.locale)
-  if (exist) {
-    exist.value = value.value
-  } else {
-    currentValues.push(value)
-  }
-
-  return currentValues
+  return currentValues.map((v) => 
+    v.locale === value.locale ? { ...v, value: value.value } : v
+  ).concat(
+    currentValues.some((v) => v.locale === value.locale) ? [] : [value]
+  )
}

Additionally, consider adding a null check for the currentValues parameter to handle edge cases.

apps/essence/server/services/db/repository/index.ts (1)

7-10: Consider using a more efficient health check query

The current health check query retrieves a full product record, which is more resource-intensive than necessary for a simple connectivity check.

Use a lighter query that doesn't retrieve actual data:

async checkHealth(): Promise<boolean> {
-  await useDatabase().query.products.findFirst()
+  // Use a more lightweight query like checking the database version or connection
+  await useDatabase().execute(sql`SELECT 1`)
  return true
}
apps/essence/server/utils/error.ts (1)

21-24: Consider including original error details in the 500 response for debugging

The current implementation logs the error but doesn't include any details in the response, which might make debugging harder.

Consider including some error information in the response for non-production environments:

return createError({
  statusCode: 500,
  statusMessage: 'Internal server error',
+  // Include error details in non-production environments
+  ...(process.env.NODE_ENV !== 'production' && {
+    data: {
+      message: exception instanceof Error ? exception.message : String(exception)
+    }
+  })
})
apps/essence/server/api/product/index.post.ts (1)

15-21: Consider adding a success response with the created product ID

Currently, the endpoint returns { ok: true } on success, but returning the created product ID or the entire product object would be more useful for clients.

     await repository.product.create({
       ...data,
       id,
       slug,
       name,
       description,
     })
 
     return {
       ok: true,
+      id,
+      slug,
     }
apps/essence/server/api/health/index.get.ts (1)

4-10: Consider adding more detailed health check information

The health check response could be more informative by including database status, version information, or other relevant details about the service.

 export default defineEventHandler(async () => {
   try {
-    await repository.checkHealth()
+    const dbStatus = await repository.checkHealth()
 
     return {
       ok: true,
+      database: {
+        status: 'connected',
+        timestamp: new Date().toISOString()
+      },
+      version: process.env.APP_VERSION || 'unknown'
     }
   } catch (error) {
     throw errorResolver(error)
apps/essence/server/api/product/[productId]/index.patch.ts (1)

34-36: Consider returning the updated product data

The endpoint currently returns { ok: true } on success, but returning the updated product would be more useful for clients.

     return {
       ok: true,
+      product: {
+        id: productId,
+        name: updatedName,
+        description: updatedDescription,
+        // Include other relevant fields
+      }
     }
apps/essence/server/services/db/repository/product.ts (3)

2-3: Unused import and inconsistent import style

The eq import from 'drizzle-orm' is duplicated - it's imported directly at line 2 but then destructured from the query builder at line 9. Only one import should be used consistently.

Since eq is already available from the function parameters in the query builder at line 9, you could remove the direct import:

 import type { ProductDraft } from '../types'
-import { eq } from 'drizzle-orm'
 import { useDatabase } from '../database'
 import { products } from '../tables'

32-34: Improve delete method to return the deleted product or boolean

The delete method returns the number of deleted rows, which is not type-safe or consistent with the other methods that return product objects.

Consider either:

  1. Returning the deleted product by using the returning() method
  2. Returning a boolean indicating success/failure
-  static async delete(id: string) {
-    return useDatabase().delete(products).where(eq(products.id, id))
+  static async delete(id: string) {
+    const [deletedProduct] = await useDatabase()
+      .delete(products)
+      .where(eq(products.id, id))
+      .returning()
+    
+    return deletedProduct
   }

7-11: Add type annotation for find method return value

The find method lacks a return type annotation, unlike the create method's parameter which has the ProductDraft type.

-  static async find(id: string) {
+  static async find(id: string): Promise<Product | null> {
     return useDatabase().query.products.findFirst({
       where: (products, { eq }) => eq(products.id, id),
     })
   }
apps/essence/shared/services/product.ts (2)

4-12: Consider adding a regex pattern for the slug field.

The product creation schema looks good with appropriate length constraints, but for the slug field, consider adding a regex pattern to ensure it only contains URL-friendly characters.

- slug: z.string().min(2).max(50).optional(),
+ slug: z.string().min(2).max(50).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'Slug must contain only lowercase letters, numbers, and hyphens').optional(),

23-39: Consider adding default values and more specific numeric validation.

The product variant schema looks comprehensive, but consider:

  1. Adding .positive() or .min(0) to numeric fields where appropriate
  2. Using more descriptive types for the weight unit enum
- weightValue: z.number(),
+ weightValue: z.number().positive('Weight value must be positive'),

- weightUnit: z.enum(['G', 'KG', 'ML', 'L', 'LB', 'OZ']),
+ weightUnit: z.enum(['G', 'KG', 'ML', 'L', 'LB', 'OZ']).$type<WeightUnit>(),

- gross: z.number(),
+ gross: z.number().min(0, 'Gross weight cannot be negative'),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 587dcfb and 203b27c.

📒 Files selected for processing (17)
  • apps/essence/drizzle.config.ts (1 hunks)
  • apps/essence/package.json (1 hunks)
  • apps/essence/server/api/health/index.get.ts (1 hunks)
  • apps/essence/server/api/product/[productId]/index.get.ts (1 hunks)
  • apps/essence/server/api/product/[productId]/index.patch.ts (1 hunks)
  • apps/essence/server/api/product/index.post.ts (1 hunks)
  • apps/essence/server/services/db/repository/index.ts (1 hunks)
  • apps/essence/server/services/db/repository/product.ts (1 hunks)
  • apps/essence/server/services/db/tables.ts (1 hunks)
  • apps/essence/server/services/db/types.ts (1 hunks)
  • apps/essence/server/utils/error.ts (1 hunks)
  • apps/essence/server/utils/locale.ts (1 hunks)
  • apps/essence/server/utils/logger.ts (1 hunks)
  • apps/essence/shared/services/locale.ts (1 hunks)
  • apps/essence/shared/services/product.ts (1 hunks)
  • apps/essence/types/index.d.ts (1 hunks)
  • docker/essence/docker-compose.yaml (1 hunks)
✅ Files skipped from review due to trivial changes (5)
  • docker/essence/docker-compose.yaml
  • apps/essence/server/utils/logger.ts
  • apps/essence/shared/services/locale.ts
  • apps/essence/drizzle.config.ts
  • apps/essence/types/index.d.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/essence/package.json
  • apps/essence/server/services/db/types.ts
🧰 Additional context used
🧬 Code Graph Analysis (5)
apps/essence/server/services/db/repository/index.ts (3)
apps/essence/server/services/db/repository/product.ts (1)
  • Product (6-35)
apps/essence/server/services/db/types.ts (1)
  • Product (6-6)
apps/essence/server/services/db/database.ts (1)
  • useDatabase (22-28)
apps/essence/server/api/health/index.get.ts (2)
apps/essence/server/services/db/repository/index.ts (1)
  • repository (13-13)
apps/essence/server/utils/error.ts (1)
  • errorResolver (6-25)
apps/essence/server/utils/error.ts (1)
apps/essence/server/utils/logger.ts (1)
  • useLogger (3-5)
apps/essence/server/api/product/index.post.ts (3)
apps/essence/shared/services/product.ts (1)
  • productCreateSchema (4-9)
apps/essence/server/services/db/repository/index.ts (1)
  • repository (13-13)
apps/essence/server/utils/error.ts (1)
  • errorResolver (6-25)
apps/essence/server/services/db/repository/product.ts (3)
apps/essence/server/services/db/types.ts (2)
  • Product (6-6)
  • ProductDraft (7-7)
apps/essence/server/services/db/database.ts (1)
  • useDatabase (22-28)
apps/essence/server/services/db/tables.ts (1)
  • products (5-14)
🪛 Biome (1.9.4)
apps/essence/server/services/db/repository/product.ts

[error] 6-35: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (6)
apps/essence/server/api/product/[productId]/index.get.ts (1)

5-11: Consider additional validation for the productId parameter

The endpoint only checks if the productId exists, but doesn't validate its format or structure.

Consider adding additional validation to ensure the productId follows the expected format (e.g., UUID):

const productId = getRouterParam(event, 'productId')
if (!productId) {
  throw createError({
    statusCode: 400,
    statusMessage: 'Product id is required',
  })
}
+
+// Validate product ID format (assuming UUID format)
+if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(productId)) {
+  throw createError({
+    statusCode: 400,
+    statusMessage: 'Invalid product id format',
+  })
+}

Need to verify if the product ID is expected to be in a specific format (UUID or otherwise) by checking the database schema and other usages in the codebase.

apps/essence/server/api/product/index.post.ts (1)

10-13:

Details

❓ Verification inconclusive

Validate slug uniqueness before creation

The code sets the slug to either the provided value or falls back to using the generated ID, but there's no check to ensure the slug is unique in the database.

Please verify if there's a unique constraint on the slug column in the database schema or if there's validation elsewhere in the system to prevent duplicate slugs.


🏁 Script executed:

#!/bin/bash
# Check if there's a unique constraint on the slug column in the database schema
rg -A 3 "slug.*unique" --glob "apps/essence/server/**/*.ts"

Length of output: 61


Validate slug uniqueness before creation

Our initial search in apps/essence/server didn’t reveal any unique constraint on the slug column. Please verify in your database schema or ORM definitions that slug is enforced as unique. In particular, check:

  • Prisma schema (e.g. schema.prisma): ensure slug String @unique
  • SQL migration files: ensure a unique index on slug
  • TypeORM entities: use @Column({ unique: true }) or @Unique(['slug']) on the slug field

If no unique constraint exists, add one—or implement an application‑level check against existing records before insertion—to prevent duplicate slugs.

apps/essence/shared/services/product.ts (2)

1-3: Imports look clean and appropriate.

The imports are concise and directly relevant to the file's purpose.


13-22: Product update schema looks good, but could benefit from the same slug validation.

The update schema appropriately makes all fields optional, which is correct for a PATCH operation.

If you implement the regex validation for slugs in the create schema, consider adding the same to the update schema.

apps/essence/server/services/db/tables.ts (2)

1-4: Imports are appropriate for Drizzle ORM schema definition.

The necessary imports for Drizzle ORM tables and relations are present.


42-49: Relations are well-defined.

The relations between tables are properly defined using Drizzle ORM's relations API.

You might want to add a relation for media to products as well, if there's a one-to-many relationship there:

export const mediaRelations = relations(media, ({ many }) => ({
  products: many(products),
}))

Comment on lines +7 to +10
async checkHealth(): Promise<boolean> {
await useDatabase().query.products.findFirst()
return true
}
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

Improve error handling in checkHealth method

The current implementation doesn't handle database errors explicitly and might give a false positive result if there's an error in the query execution.

Add proper error handling to accurately report database health:

async checkHealth(): Promise<boolean> {
-  await useDatabase().query.products.findFirst()
-  return true
+  try {
+    await useDatabase().query.products.findFirst()
+    return true
+  } catch (error) {
+    // Log the error or handle it as needed
+    return false
+  }
}
📝 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
async checkHealth(): Promise<boolean> {
await useDatabase().query.products.findFirst()
return true
}
async checkHealth(): Promise<boolean> {
try {
await useDatabase().query.products.findFirst()
return true
} catch (error) {
// Log the error or handle it as needed
return false
}
}

Comment on lines +7 to +12
if (exception instanceof ZodError) {
throw createError({
statusCode: 400,
statusMessage: 'Invalid data',
})
}
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

Enhance ZodError handling to include validation details

Currently, when a ZodError occurs, the error response only includes a generic "Invalid data" message without details about what validation failed.

Include validation error details in the response to help API consumers understand what went wrong:

if (exception instanceof ZodError) {
  throw createError({
    statusCode: 400,
    statusMessage: 'Invalid data',
+    data: {
+      errors: exception.errors.map(error => ({
+        path: error.path.join('.'),
+        message: error.message
+      }))
+    }
  })
}
📝 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
if (exception instanceof ZodError) {
throw createError({
statusCode: 400,
statusMessage: 'Invalid data',
})
}
if (exception instanceof ZodError) {
throw createError({
statusCode: 400,
statusMessage: 'Invalid data',
data: {
errors: exception.errors.map(error => ({
path: error.path.join('.'),
message: error.message
}))
}
})
}

Comment on lines +1 to +3
import { H3Error } from 'h3'
import { ZodError } from 'zod'

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

Missing import for createError and useLogger

The code uses createError and useLogger functions but doesn't import them.

Add the missing imports:

import { H3Error } from 'h3'
import { ZodError } from 'zod'
+import { createError } from 'h3'
+import { useLogger } from '~/server/utils/logger'
📝 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
import { H3Error } from 'h3'
import { ZodError } from 'zod'
import { H3Error } from 'h3'
import { ZodError } from 'zod'
import { createError } from 'h3'
import { useLogger } from '~/server/utils/logger'

Comment on lines +1 to +2
import { repository } from '~~/server/services/db/repository'

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

Missing imports for errorResolver, getRouterParam, and createError

The code uses functions that aren't imported.

Add the missing imports:

import { repository } from '~~/server/services/db/repository'
+import { errorResolver } from '~~/server/utils/error'
+import { getRouterParam, createError } from 'h3'
📝 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
import { repository } from '~~/server/services/db/repository'
import { repository } from '~~/server/services/db/repository'
import { errorResolver } from '~~/server/utils/error'
import { getRouterParam, createError } from 'h3'

})
}

return repository.product.find(productId)
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

Add handling for product not found scenario

The current implementation doesn't handle the case where a product with the given ID doesn't exist.

Add proper handling for the "not found" scenario:

-  return repository.product.find(productId)
+  const product = await repository.product.find(productId)
+  
+  if (!product) {
+    throw createError({
+      statusCode: 404,
+      statusMessage: 'Product not found',
+    })
+  }
+  
+  return product
📝 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
return repository.product.find(productId)
const product = await repository.product.find(productId)
if (!product) {
throw createError({
statusCode: 404,
statusMessage: 'Product not found',
})
}
return product

Comment on lines +6 to +35
export class Product {
static async find(id: string) {
return useDatabase().query.products.findFirst({
where: (products, { eq }) => eq(products.id, id),
})
}

static async create(data: ProductDraft) {
const [product] = await useDatabase()
.insert(products)
.values(data)
.returning()

return product
}

static async update(id: string, data: Partial<ProductDraft>) {
const [product] = await useDatabase()
.update(products)
.set(data)
.where(eq(products.id, id))
.returning()

return product
}

static async delete(id: string) {
return useDatabase().delete(products).where(eq(products.id, 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.

🛠️ Refactor suggestion

Convert class with static methods to plain functions

The static analysis tool correctly identifies that this class only contains static methods. Following modern JavaScript/TypeScript practices, consider refactoring to use plain functions instead.

 import type { ProductDraft } from '../types'
-import { eq } from 'drizzle-orm'
+import { eq } from 'drizzle-orm'
 import { useDatabase } from '../database'
 import { products } from '../tables'

-export class Product {
-  static async find(id: string) {
-    return useDatabase().query.products.findFirst({
-      where: (products, { eq }) => eq(products.id, id),
-    })
-  }
+export async function findProduct(id: string) {
+  return useDatabase().query.products.findFirst({
+    where: (products, { eq }) => eq(products.id, id),
+  })
+}

-  static async create(data: ProductDraft) {
-    const [product] = await useDatabase()
-      .insert(products)
-      .values(data)
-      .returning()
+export async function createProduct(data: ProductDraft) {
+  const [product] = await useDatabase()
+    .insert(products)
+    .values(data)
+    .returning()

-    return product
-  }
+  return product
+}

-  static async update(id: string, data: Partial<ProductDraft>) {
-    const [product] = await useDatabase()
-      .update(products)
-      .set(data)
-      .where(eq(products.id, id))
-      .returning()
+export async function updateProduct(id: string, data: Partial<ProductDraft>) {
+  const [product] = await useDatabase()
+    .update(products)
+    .set(data)
+    .where(eq(products.id, id))
+    .returning()

-    return product
-  }
+  return product
+}

-  static async delete(id: string) {
-    return useDatabase().delete(products).where(eq(products.id, id))
-  }
+export async function deleteProduct(id: string) {
+  return useDatabase().delete(products).where(eq(products.id, id))
+}
-}
📝 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
export class Product {
static async find(id: string) {
return useDatabase().query.products.findFirst({
where: (products, { eq }) => eq(products.id, id),
})
}
static async create(data: ProductDraft) {
const [product] = await useDatabase()
.insert(products)
.values(data)
.returning()
return product
}
static async update(id: string, data: Partial<ProductDraft>) {
const [product] = await useDatabase()
.update(products)
.set(data)
.where(eq(products.id, id))
.returning()
return product
}
static async delete(id: string) {
return useDatabase().delete(products).where(eq(products.id, id))
}
}
import type { ProductDraft } from '../types'
import { eq } from 'drizzle-orm'
import { useDatabase } from '../database'
import { products } from '../tables'
export async function findProduct(id: string) {
return useDatabase().query.products.findFirst({
where: (products, { eq }) => eq(products.id, id),
})
}
export async function createProduct(data: ProductDraft) {
const [product] = await useDatabase()
.insert(products)
.values(data)
.returning()
return product
}
export async function updateProduct(id: string, data: Partial<ProductDraft>) {
const [product] = await useDatabase()
.update(products)
.set(data)
.where(eq(products.id, id))
.returning()
return product
}
export async function deleteProduct(id: string) {
return useDatabase().delete(products).where(eq(products.id, id))
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 6-35: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)

Comment thread apps/essence/shared/services/product.ts Outdated
Comment on lines +40 to +55
export const productVariantUpdateSchema = z.object({
locale,
name: z.string().min(2).max(50).optional(),
weightValue: z.number().optional(),
weightUnit: z.enum(['G', 'KG', 'ML', 'L', 'LB', 'OZ']).optional(),
gross: z.number().optional(),
net: z.number().optional(),
calories: z.coerce.number().nullable().optional(),
protein: z.coerce.number().nullable().optional(),
fat: z.coerce.number().nullable().optional(),
carbohydrate: z.coerce.number().nullable().optional(),
sku: z.string().max(50).optional(),
})

export type ProductVariantUpdateSchema = z.output<typeof productVariantUpdateSchema>

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

Consider consistency in nullable vs. optional fields.

The update schema includes .nullable() for nutritional values but not for other numeric fields. This creates an inconsistency - some fields can be explicitly set to null while others cannot.

Either make all optional numeric fields nullable or none of them:

- gross: z.number().optional(),
+ gross: z.number().nullable().optional(),

- net: z.number().optional(),
+ net: z.number().nullable().optional(),

Or document why only nutritional values can be null but other fields cannot.

📝 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
export const productVariantUpdateSchema = z.object({
locale,
name: z.string().min(2).max(50).optional(),
weightValue: z.number().optional(),
weightUnit: z.enum(['G', 'KG', 'ML', 'L', 'LB', 'OZ']).optional(),
gross: z.number().optional(),
net: z.number().optional(),
calories: z.coerce.number().nullable().optional(),
protein: z.coerce.number().nullable().optional(),
fat: z.coerce.number().nullable().optional(),
carbohydrate: z.coerce.number().nullable().optional(),
sku: z.string().max(50).optional(),
})
export type ProductVariantUpdateSchema = z.output<typeof productVariantUpdateSchema>
export const productVariantUpdateSchema = z.object({
locale,
name: z.string().min(2).max(50).optional(),
weightValue: z.number().optional(),
weightUnit: z.enum(['G', 'KG', 'ML', 'L', 'LB', 'OZ']).optional(),
gross: z.number().nullable().optional(),
net: z.number().nullable().optional(),
calories: z.coerce.number().nullable().optional(),
protein: z.coerce.number().nullable().optional(),
fat: z.coerce.number().nullable().optional(),
carbohydrate: z.coerce.number().nullable().optional(),
sku: z.string().max(50).optional(),
})
export type ProductVariantUpdateSchema = z.output<typeof productVariantUpdateSchema>

Comment on lines +5 to +14
export const products = pgTable('products', {
id: cuid2('id').defaultRandom().primaryKey(),
createdAt: timestamp('created_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
slug: varchar('slug').notNull(),
isAvailableForPurchase: boolean('is_available_for_purchase').notNull().default(true),
name: jsonb('name').notNull().default([]).$type<LocaleValue[]>(),
description: jsonb('description').notNull().default([]).$type<LocaleValue[]>(),
mediaId: cuid2('media_id').references(() => media.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

Missing type definition for LocaleValue.

The LocaleValue[] type is used but not imported or defined in this file. This may cause TypeScript errors.

+ import type { LocaleValue } from '../../types' // Adjust the import path as needed

Also, consider adding a unique constraint to the slug field since it's likely used for URL routing:

- slug: varchar('slug').notNull(),
+ slug: varchar('slug').notNull().unique(),
📝 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
export const products = pgTable('products', {
id: cuid2('id').defaultRandom().primaryKey(),
createdAt: timestamp('created_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
slug: varchar('slug').notNull(),
isAvailableForPurchase: boolean('is_available_for_purchase').notNull().default(true),
name: jsonb('name').notNull().default([]).$type<LocaleValue[]>(),
description: jsonb('description').notNull().default([]).$type<LocaleValue[]>(),
mediaId: cuid2('media_id').references(() => media.id),
})
import type { LocaleValue } from '../../types' // Adjust the path if needed
export const products = pgTable('products', {
id: cuid2('id').defaultRandom().primaryKey(),
createdAt: timestamp('created_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
slug: varchar('slug').notNull().unique(),
isAvailableForPurchase: boolean('is_available_for_purchase').notNull().default(true),
name: jsonb('name').notNull().default([]).$type<LocaleValue[]>(),
description: jsonb('description').notNull().default([]).$type<LocaleValue[]>(),
mediaId: cuid2('media_id').references(() => media.id),
})

Comment on lines +16 to +34
export const productVariants = pgTable('product_variants', {
id: cuid2('id').defaultRandom().primaryKey(),
createdAt: timestamp('created_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
name: jsonb('name').notNull().default([]).$type<LocaleValue[]>(),
weightValue: numeric('weight_value', { mode: 'number' }).notNull(),
weightUnit: varchar('weight_unit').notNull().$type<WeightUnit>(),
gross: numeric('gross', { mode: 'number' }).notNull(),
net: numeric('net', { mode: 'number' }),
calories: numeric('calories', { mode: 'number' }),
protein: numeric('protein', { mode: 'number' }),
fat: numeric('fat', { mode: 'number' }),
carbohydrate: numeric('carbohydrate', { mode: 'number' }),
sku: varchar('sku'),
productId: cuid2('product_id').notNull().references(() => products.id, {
onDelete: 'cascade',
onUpdate: 'cascade',
}),
})
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

Missing WeightUnit type and consider adding indexes.

The WeightUnit type is used but not imported or defined. Also, for performance optimization, consider adding indexes to foreign keys and frequently queried fields.

+ import type { LocaleValue, WeightUnit } from '../../types' // Adjust the import path as needed

Additionally, consider adding an index to the productId field since it will be frequently used in JOIN operations:

  productId: cuid2('product_id').notNull().references(() => products.id, {
    onDelete: 'cascade',
    onUpdate: 'cascade',
- }),
+ }).$index(),

Consider adding a unique constraint for the SKU if it needs to be unique:

- sku: varchar('sku'),
+ sku: varchar('sku').unique(),

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

Comment on lines +36 to +40
export const media = pgTable('media', {
id: cuid2('id').defaultRandom().primaryKey(),
createdAt: timestamp('created_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
})
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

Media table lacks essential fields.

The media table is extremely minimal and missing important fields for a media entity, such as file path, type, size, etc.

Consider enhancing the media table with fields that would be necessary for managing media assets:

export const media = pgTable('media', {
  id: cuid2('id').defaultRandom().primaryKey(),
  createdAt: timestamp('created_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
  updatedAt: timestamp('updated_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
+ filePath: varchar('file_path').notNull(),
+ fileType: varchar('file_type').notNull(),
+ fileSize: numeric('file_size', { mode: 'number' }).notNull(),
+ altText: varchar('alt_text'),
})
📝 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
export const media = pgTable('media', {
id: cuid2('id').defaultRandom().primaryKey(),
createdAt: timestamp('created_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
})
export const media = pgTable('media', {
id: cuid2('id').defaultRandom().primaryKey(),
createdAt: timestamp('created_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { precision: 3, mode: 'string' }).notNull().defaultNow(),
filePath: varchar('file_path').notNull(),
fileType: varchar('file_type').notNull(),
fileSize: numeric('file_size', { mode: 'number' }).notNull(),
altText: varchar('alt_text'),
})

@sonarqubecloud
Copy link
Copy Markdown

@hmbanan666 hmbanan666 added the enhancement New feature or request label Apr 19, 2025
@hmbanan666 hmbanan666 merged commit c8aac48 into main Apr 19, 2025
5 checks passed
@hmbanan666 hmbanan666 deleted the essence branch April 19, 2025 14:26
@coderabbitai coderabbitai Bot mentioned this pull request May 7, 2025
@coderabbitai coderabbitai Bot mentioned this pull request May 30, 2026
Closed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant