feat(q&a): localized questions, new schema and seeding#71
Conversation
✅ Deploy Preview for develop-devlovers ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
WalkthroughThis PR refactors the question and category system to support localization and introduces a new database schema. Changes include: locale-aware API routes with pagination and search, modernized schema with UUID keys and composite constraints, restructured seed scripts separating category and question seeding, middleware updates to propagate locale headers, and component updates to handle locale-driven requests. Changes
Sequence DiagramsequenceDiagram
participant Client
participant Proxy
participant APIRoute as API Route<br/>[category]
participant DB as Database
Client->>Proxy: GET /uk/q&a?category=...&search=...
Proxy->>Proxy: Extract locale (uk)<br/>or default to 'uk'
Proxy->>APIRoute: Forward + x-locale header
APIRoute->>APIRoute: Read locale from<br/>query or header
rect rgb(240, 250, 255)
note right of APIRoute: Locale-aware Query
APIRoute->>APIRoute: Build WHERE:<br/>categorySlug + locale
APIRoute->>APIRoute: Apply search filter<br/>(ilike if present)
APIRoute->>APIRoute: Order by sortOrder
end
APIRoute->>DB: SELECT count, rows<br/>WHERE categorySlug = slug<br/>AND locale = locale
DB->>APIRoute: Return filtered results
APIRoute->>APIRoute: Compute pagination<br/>(page, limit, total)
APIRoute->>Client: { results, locale,<br/>page, totalPages }
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (8)
frontend/db/seed-questions.ts (2)
6-16: Consider stronger typing for answerBlocks to prevent data integrity issues.The
answerBlocksfield is typed asunknown, which bypasses validation of the JSON structure being inserted into the database. This could lead to runtime errors if the data doesn't match the expected format used by the frontend.🔎 Proposed type definition
+type AnswerBlock = { + // Define the expected structure based on frontend usage + // Example structure (adjust as needed): + type: 'text' | 'code' | 'list'; + content: string; + language?: string; +}; + type RawQuestion = { category: string; order?: number; translations: Record< string, { question: string; - answerBlocks: unknown; + answerBlocks: AnswerBlock[]; } >; };
26-34: Consider adding validation for locale keys and required fields.The script assumes all translations contain valid locale keys and required fields. Missing validation could lead to incomplete or malformed data in the database.
🔎 Proposed validation
+const VALID_LOCALES = ['en', 'pl', 'uk'] as const; + const rows = data.flatMap(q => Object.entries(q.translations).map(([locale, content]) => { + if (!VALID_LOCALES.includes(locale as any)) { + console.warn(`Skipping invalid locale "${locale}" for category "${q.category}"`); + return null; + } + if (!content.question || !content.answerBlocks) { + console.warn(`Missing required fields for locale "${locale}" in category "${q.category}"`); + return null; + } return { categorySlug: q.category, locale, sortOrder: q.order ?? 0, question: content.question, answerBlocks: content.answerBlocks, }; }) - ); + ).filter(Boolean);frontend/db/seed-categories.ts (1)
7-7: Consider centralizing the LOCALES constant to avoid duplication.The
LOCALESconstant is defined here and likely in other seed scripts (e.g.,seed-questions.tsvalidation). Centralizing it in a shared config file would improve maintainability.🔎 Proposed centralization
Create a new file
frontend/config/locales.ts:export const LOCALES = ['en', 'pl', 'uk'] as const; export type Locale = typeof LOCALES[number];Then import and use it in seed scripts:
+import { LOCALES } from '../config/locales'; -const LOCALES = ['en', 'pl', 'uk'] as const;frontend/app/api/questions/[category]/route.ts (1)
37-39: Consider performance implications of ilike queries on large datasets.The
ilikeoperator performs case-insensitive pattern matching, which can be slow on large tables without proper indexing. Consider adding a database index on thequestioncolumn or implementing full-text search for better performance.Recommended database optimization:
Add a GIN index for pattern matching:
CREATE INDEX idx_questions_question_gin ON questions USING gin(question gin_trgm_ops);(Requires the
pg_trgmextension)Alternatively, implement PostgreSQL full-text search:
ALTER TABLE questions ADD COLUMN question_tsv tsvector; CREATE INDEX idx_questions_fts ON questions USING gin(question_tsv);Or limit search to the already-filtered result set by adding pagination before search filtering (trade-off: less accurate results).
frontend/utils/slugify.ts (1)
1-8: Implement consecutive hyphen deduplication, leading/trailing hyphen removal, and consider Unicode normalization for category names.The current implementation produces incorrect slugs with leading or trailing hyphens when input starts or ends with special characters (e.g., "-test-name" → "-test-name"). The suggested improvements address this:
export function slugify(value: string) { return value .toLowerCase() .trim() .replace(/\s+/g, '-') .replace(/\./g, '') - .replace(/[^a-z0-9-]/g, ''); + .replace(/[^a-z0-9-]/g, '') + .replace(/-+/g, '-') + .replace(/^-+|-+$/g, ''); }For better Unicode handling (e.g., "Café" or non-Latin category names), consider using a dedicated library like
slugifyfrom npm.frontend/components/q&a/TabsSection.tsx (2)
94-99: Missing response status check before parsing JSON.If the API returns an error status (4xx/5xx), calling
res.json()may throw or return unexpected data. Checkres.okbefore parsing.🔎 Proposed fix
const res = await fetch( `/api/questions/${active}?page=${currentPage}&limit=10&locale=${locale}${searchParam}` ); + if (!res.ok) { + throw new Error(`HTTP ${res.status}`); + } + const data: PaginatedResponse = await res.json();
167-170: Loading spinner may be invisible due to missing border color.
border-b-2without a color class relies on the default border color, which may not be visible against certain backgrounds.🔎 Proposed fix
<div className="flex justify-center py-12"> - <div className="animate-spin h-8 w-8 border-b-2" /> + <div className="animate-spin h-8 w-8 border-b-2 border-gray-600 rounded-full" /> </div>frontend/drizzle/schema.ts (1)
201-211: Consider adding unique constraint onThe
notNullbut lacks a unique constraint, which could allow duplicate user accounts. If email should be unique per user, add a unique constraint.🔎 Proposed fix
export const user = pgTable('user', { id: text('id').primaryKey().notNull(), name: text(), - email: text().notNull(), + email: text().notNull().unique(), emailVerified: timestamp('email_verified', { mode: 'string' }), image: text(), role: text().default('user'), points: integer().default(0), preferredLocale: varchar('preferred_locale', { length: 5 }).default('en'), createdAt: timestamp('created_at', { mode: 'string' }).defaultNow().notNull(), });
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
frontend/app/api/questions/[category]/route.ts(1 hunks)frontend/components/q&a/TabsSection.tsx(6 hunks)frontend/db/schema/categories.ts(1 hunks)frontend/db/schema/index.ts(1 hunks)frontend/db/schema/questions.ts(1 hunks)frontend/db/seed-categories.ts(1 hunks)frontend/db/seed-questions.ts(1 hunks)frontend/db/seed.ts(0 hunks)frontend/drizzle/schema.ts(1 hunks)frontend/package.json(1 hunks)frontend/proxy.ts(4 hunks)frontend/utils/slugify.ts(1 hunks)
💤 Files with no reviewable changes (1)
- frontend/db/seed.ts
🧰 Additional context used
🧬 Code graph analysis (6)
frontend/db/seed-questions.ts (2)
frontend/db/index.ts (1)
db(17-17)frontend/db/schema/questions.ts (1)
questions(11-28)
frontend/db/schema/questions.ts (1)
frontend/drizzle/schema.ts (1)
questions(32-47)
frontend/db/schema/categories.ts (2)
frontend/db/seed.ts (1)
main(19-50)frontend/drizzle/relations.ts (1)
category(4-9)
frontend/app/api/questions/[category]/route.ts (1)
frontend/db/schema/questions.ts (1)
questions(11-28)
frontend/db/seed-categories.ts (5)
frontend/data/category.ts (1)
categoryNames(1-9)frontend/utils/slugify.ts (1)
slugify(1-8)frontend/db/index.ts (1)
db(17-17)frontend/db/schema/categories.ts (1)
categories(3-14)frontend/drizzle/schema.ts (1)
categories(19-30)
frontend/drizzle/schema.ts (2)
frontend/db/schema/categories.ts (1)
categories(3-14)frontend/db/schema/questions.ts (1)
questions(11-28)
🔇 Additional comments (6)
frontend/proxy.ts (1)
22-23: LGTM! Locale handling is now consistent and robust.The updated regex pattern with lookahead
(?=\/|$)correctly strips locale prefixes only when they appear at the start of the path. The consistent fallback to'uk'across all extraction points and the newx-localeheader propagation enable downstream handlers (like the questions API) to access locale information reliably.Also applies to: 30-30, 40-40, 50-50, 60-60, 69-69
frontend/db/schema/index.ts (1)
2-2: LGTM! Schema export updated correctly.The new questions schema is now properly exposed through the index barrel export.
frontend/package.json (1)
11-11: LGTM! Seed script renamed appropriately.The focused
seed:questionsscript aligns with the new dedicated seeding approach introduced in this PR.frontend/db/seed-categories.ts (1)
9-23: LGTM! Efficient batched seeding with proper conflict handling.The batched insert approach with
onConflictDoNothingefficiently handles multi-locale category seeding and prevents duplicate entries via the schema's unique constraint on(slug, locale).frontend/db/schema/categories.ts (1)
3-14: LGTM! Well-designed locale-aware schema.The UUID-based schema with composite unique constraint on
(slug, locale)properly supports multi-locale categories. The varchar lengths (50 for slug, 5 for locale) are appropriate for the use case.frontend/app/api/questions/[category]/route.ts (1)
14-77: LGTM! Comprehensive error handling and locale-aware pagination.The try-catch wrapper, default error response with proper status code, and inclusion of locale in responses provide good error resilience. The pagination logic with bounds checking (max 50 items, min 1) prevents abuse.
| const locale = | ||
| searchParams.get('locale') || | ||
| req.headers.get('x-locale') || | ||
| DEFAULT_LOCALE; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Add validation for locale parameter to prevent invalid values.
The locale is accepted from query parameters or headers without validation. Invalid locale values could lead to unexpected behavior or empty result sets.
🔎 Proposed locale validation
+const VALID_LOCALES = ['en', 'pl', 'uk'] as const;
+
const locale =
searchParams.get('locale') ||
req.headers.get('x-locale') ||
DEFAULT_LOCALE;
+
+if (!VALID_LOCALES.includes(locale as any)) {
+ return NextResponse.json(
+ { error: `Invalid locale: ${locale}` },
+ { status: 400 }
+ );
+}📝 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.
| const locale = | |
| searchParams.get('locale') || | |
| req.headers.get('x-locale') || | |
| DEFAULT_LOCALE; | |
| const VALID_LOCALES = ['en', 'pl', 'uk'] as const; | |
| const locale = | |
| searchParams.get('locale') || | |
| req.headers.get('x-locale') || | |
| DEFAULT_LOCALE; | |
| if (!VALID_LOCALES.includes(locale as any)) { | |
| return NextResponse.json( | |
| { error: `Invalid locale: ${locale}` }, | |
| { status: 400 } | |
| ); | |
| } |
🤖 Prompt for AI Agents
In frontend/app/api/questions/[category]/route.ts around lines 25 to 28, the
locale pulled from searchParams or x-locale header is used without validation;
validate the resolved locale against your allowed list (e.g. SUPPORTED_LOCALES
or a constant array including DEFAULT_LOCALE) and if the value is not in that
list, replace it with DEFAULT_LOCALE (or return a 400 Bad Request if you prefer
strict validation); implement the check immediately after computing locale and
ensure downstream code only sees a validated locale value.
| const search = searchParams.get('search')?.trim(); | ||
|
|
||
| const baseCondition = and( | ||
| eq(questions.categorySlug, category.toLowerCase()), | ||
| eq(questions.locale, locale) | ||
| ); | ||
|
|
||
| const baseCondition = eq(questions.categoryId, cat[0].id); | ||
| const whereCondition = search | ||
| ? and(baseCondition, ilike(questions.question, `%${search}%`)) | ||
| : baseCondition; | ||
| const whereCondition = search | ||
| ? and(baseCondition, ilike(questions.question, `%${search}%`)) | ||
| : baseCondition; |
There was a problem hiding this comment.
Escape SQL wildcard characters in the search parameter.
Drizzle ORM's ilike() function uses parameterized queries, which properly protects against SQL injection. However, user input containing % or _ characters will be interpreted as LIKE wildcards rather than literals, causing unexpected search behavior. Sanitize the search input by escaping these characters:
-const search = searchParams.get('search')?.trim();
+const search = searchParams.get('search')?.trim().replace(/[%_]/g, '\\$&');This ensures that literal % and _ characters in user input are treated as text, not pattern wildcards.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
frontend/app/api/questions/[category]/route.ts lines 30-39: the search string
can contain '%' or '_' which LIKE/ILIKE treat as wildcards; before building the
ilike pattern, escape backslashes, percent and underscore in the trimmed search
input by prefixing them with a backslash (e.g., replace \ with \\ then % with \%
and _ with \_), then use that escaped value inside your `%${escaped}%` ilike
pattern so user-supplied literal % and _ are treated as text rather than
wildcards.
| const baseCondition = and( | ||
| eq(questions.categorySlug, category.toLowerCase()), | ||
| eq(questions.locale, locale) | ||
| ); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Validate and sanitize the category slug to prevent injection and errors.
The category parameter from the URL is lowercased but not validated. This could allow malicious or malformed input to reach the database query.
🔎 Proposed category validation
+// Add at the top of the function
+const categorySlugPattern = /^[a-z0-9-]+$/;
+
const { category } = await ctx.params;
+
+if (!categorySlugPattern.test(category.toLowerCase())) {
+ return NextResponse.json(
+ { error: 'Invalid category format' },
+ { status: 400 }
+ );
+}
+
const { searchParams } = new URL(req.url);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
frontend/app/api/questions/[category]/route.ts around lines 32 to 35: the
category slug from the URL is only lowercased and not validated, which can allow
malformed or malicious input into the DB query; validate and sanitize it before
use by decoding and trimming the input, enforce a strict pattern/length (e.g.,
allow only lowercase letters, numbers, hyphens, max length ~100) or similar
project-specific slug regex, reject invalid slugs with a 400 response (or
normalize acceptable characters), and only then use the sanitizedLowercasedSlug
in the query.
| const params = useParams(); | ||
|
|
||
| const locale = params.locale as string; |
There was a problem hiding this comment.
Unsafe type assertion for locale parameter.
useParams() returns Params where values can be string | string[] | undefined. The direct cast as string bypasses type checking and could lead to runtime issues if locale is missing or malformed.
🔎 Proposed fix with validation
const params = useParams();
-
- const locale = params.locale as string;
+ const locale = typeof params.locale === 'string' ? params.locale : 'en';📝 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.
| const params = useParams(); | |
| const locale = params.locale as string; | |
| const params = useParams(); | |
| const locale = typeof params.locale === 'string' ? params.locale : 'en'; |
🤖 Prompt for AI Agents
In frontend/components/q&a/TabsSection.tsx around lines 28 to 30, the code
unsafely asserts locale with `as string`; instead validate the value returned
from useParams() (it can be string | string[] | undefined) by checking typeof
params.locale === 'string' and that it is not empty before using it, handle
string[] by selecting a sensible element (e.g., first) or rejecting it, and
provide a safe fallback (default locale or early return/error handling) so no
direct type assertion is used.
| export const questions = pgTable( | ||
| 'questions', | ||
| { | ||
| id: uuid('id').defaultRandom().primaryKey(), | ||
| categorySlug: varchar('category_slug', { length: 50 }).notNull(), | ||
| locale: varchar('locale', { length: 5 }).notNull(), | ||
| question: text('question').notNull(), | ||
| answerBlocks: jsonb('answer_blocks').notNull(), | ||
| sortOrder: integer('sort_order').default(0), | ||
| }, | ||
| table => [ | ||
| unique('questions_category_locale_order_unique').on( | ||
| table.categorySlug, | ||
| table.locale, | ||
| table.sortOrder | ||
| ), | ||
| ] | ||
| ); |
There was a problem hiding this comment.
Schema inconsistency: Two different questions table definitions exist.
This schema uses categorySlug (varchar) without a foreign key, while frontend/drizzle/schema.ts lines 32-47 defines a different questions table using categoryId (uuid) with a foreign key to categories. This creates ambiguity about which schema is authoritative and could cause migration/query issues.
Additionally, consider:
- Adding a foreign key constraint to
categories(slug, locale)for referential integrity - Making
sortOrderexplicitly.notNull()since it's part of a unique constraint—null values can bypass uniqueness checks
🔎 Proposed fix to add foreign key and make sortOrder notNull
+import { categories } from './categories';
+import { foreignKey } from 'drizzle-orm/pg-core';
+
export const questions = pgTable(
'questions',
{
id: uuid('id').defaultRandom().primaryKey(),
categorySlug: varchar('category_slug', { length: 50 }).notNull(),
locale: varchar('locale', { length: 5 }).notNull(),
question: text('question').notNull(),
answerBlocks: jsonb('answer_blocks').notNull(),
- sortOrder: integer('sort_order').default(0),
+ sortOrder: integer('sort_order').default(0).notNull(),
},
table => [
unique('questions_category_locale_order_unique').on(
table.categorySlug,
table.locale,
table.sortOrder
),
+ foreignKey({
+ columns: [table.categorySlug, table.locale],
+ foreignColumns: [categories.slug, categories.locale],
+ name: 'questions_category_slug_locale_fk',
+ }).onDelete('cascade'),
]
);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In frontend/db/schema/questions.ts around lines 11 to 28 there is a conflicting
schema vs frontend/drizzle/schema.ts: this file uses categorySlug (varchar)
while the other file uses categoryId (uuid) with a FK to categories, and
sortOrder is nullable despite being part of a unique constraint; reconcile the
two by choosing one authoritative design (prefer using categoryId uuid
referencing categories.id for consistency with the other schema) or, if you must
keep categorySlug, add a proper foreign key to categories (categories.slug +
categories.locale) to enforce referential integrity, and make sortOrder
.notNull() so the unique index cannot be bypassed by NULLs; update the table
definition to match the chosen approach and remove the duplicate/ambiguous
schema elsewhere so both files agree.
| export const questions = pgTable( | ||
| 'questions', | ||
| { | ||
| id: uuid('id').defaultRandom().primaryKey().notNull(), | ||
| question: text('question').notNull(), | ||
| answerBlocks: jsonb('answer_blocks').notNull(), | ||
| categoryId: uuid('category_id').notNull(), | ||
| }, | ||
| table => [ | ||
| foreignKey({ | ||
| columns: [table.categoryId], | ||
| foreignColumns: [categories.id], | ||
| name: 'questions_category_id_categories_id_fk', | ||
| }).onDelete('cascade'), | ||
| ] | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Explore file structure to locate the schema files
echo "=== Looking for schema files ==="
fd -type f -name "schema.ts" | head -20
echo -e "\n=== Looking for questions schema files ==="
fd -type f -path "*questions*" -name "*.ts" | head -20
echo -e "\n=== Checking frontend/drizzle/schema.ts exists ==="
test -f "frontend/drizzle/schema.ts" && echo "EXISTS" || echo "NOT FOUND"
echo -e "\n=== Checking frontend/db/schema/questions.ts exists ==="
test -f "frontend/db/schema/questions.ts" && echo "EXISTS" || echo "NOT FOUND"Repository: DevLoversTeam/devlovers.net
Length of output: 629
🏁 Script executed:
#!/bin/bash
# Read both schema files to compare
echo "=== frontend/drizzle/schema.ts (lines around questions) ==="
cat -n frontend/drizzle/schema.ts | head -100
echo -e "\n=== frontend/db/schema/questions.ts ==="
cat -n frontend/db/schema/questions.tsRepository: DevLoversTeam/devlovers.net
Length of output: 4379
🏁 Script executed:
#!/bin/bash
# Check which schema files are imported/used
echo "=== Imports of drizzle schema.ts ==="
rg -l "from.*drizzle/schema" frontend --type ts
echo -e "\n=== Imports of db/schema/questions ==="
rg -l "from.*db/schema/questions" frontend --type ts
echo -e "\n=== All imports of 'questions' table ==="
rg "import.*questions" frontend --type ts -A 1
echo -e "\n=== Drizzle migrations folder ==="
ls -la frontend/drizzle/migrations 2>/dev/null | head -20 || echo "Migrations folder not found or empty"
echo -e "\n=== Check drizzle.config.ts ==="
cat frontend/drizzle.config.ts 2>/dev/null || cat drizzle.config.ts 2>/dev/null || echo "Config not found"Repository: DevLoversTeam/devlovers.net
Length of output: 1165
🏁 Script executed:
#!/bin/bash
# Check db/schema/index.ts to see authoritative schema
echo "=== frontend/db/schema/index.ts ==="
cat -n frontend/db/schema/index.ts
echo -e "\n=== Check if frontend/drizzle/schema.ts is actually used anywhere ==="
rg "from.*frontend/drizzle/schema\|from ['\"].*drizzle/schema" frontend --type ts
echo -e "\n=== Check migrations in drizzle folder ==="
find frontend/drizzle -name "*.sql" 2>/dev/null | head -5
echo -e "\n=== Check drizzle folder structure ==="
ls -la frontend/drizzle/ 2>/dev/nullRepository: DevLoversTeam/devlovers.net
Length of output: 1393
🏁 Script executed:
#!/bin/bash
# Check the migrations to see which schema they match
echo "=== First migration (questions table) ==="
head -50 frontend/drizzle/0000_fair_quasar.sql
echo -e "\n=== Check if migrations match categoryId or categorySlug ==="
grep -n "category" frontend/drizzle/*.sql
echo -e "\n=== Verify which API is actually being used ==="
cat -n frontend/app/api/questions/\[category\]/route.ts | head -40Repository: DevLoversTeam/devlovers.net
Length of output: 4389
Remove duplicate questions table definition from frontend/drizzle/schema.ts.
This file contains a conflicting questions schema that differs fundamentally from the authoritative definition in frontend/db/schema/questions.ts. The drizzle.config.ts explicitly configures migrations to use ./db/schema/index.ts, making frontend/db/schema/questions.ts the source of truth. The API route frontend/app/api/questions/[category]/route.ts imports and queries using the correct schema (with categorySlug, locale, and sortOrder columns), not the conflicting definition in frontend/drizzle/schema.ts. Remove the duplicate schema or reconcile it with the actual authoritative version.
| index('quiz_answers_question_display_order_idx').using( | ||
| 'btree', | ||
| table.quizQuestionId.asc().nullsLast().op('int4_ops'), | ||
| table.displayOrder.asc().nullsLast().op('int4_ops') | ||
| ), |
There was a problem hiding this comment.
Incorrect operator class for UUID column in index.
quizQuestionId is a uuid column but uses int4_ops. This should be uuid_ops to match the column type.
🔎 Proposed fix
index('quiz_answers_question_display_order_idx').using(
'btree',
- table.quizQuestionId.asc().nullsLast().op('int4_ops'),
+ table.quizQuestionId.asc().nullsLast().op('uuid_ops'),
table.displayOrder.asc().nullsLast().op('int4_ops')
),📝 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.
| index('quiz_answers_question_display_order_idx').using( | |
| 'btree', | |
| table.quizQuestionId.asc().nullsLast().op('int4_ops'), | |
| table.displayOrder.asc().nullsLast().op('int4_ops') | |
| ), | |
| index('quiz_answers_question_display_order_idx').using( | |
| 'btree', | |
| table.quizQuestionId.asc().nullsLast().op('uuid_ops'), | |
| table.displayOrder.asc().nullsLast().op('int4_ops') | |
| ), |
🤖 Prompt for AI Agents
In frontend/drizzle/schema.ts around lines 58 to 62, the index definition uses
the wrong operator class for the uuid column quizQuestionId (int4_ops); update
the index to use uuid_ops for quizQuestionId while keeping displayOrder as
int4_ops (or remove explicit op for displayOrder if unnecessary), i.e., change
the operator class on the quizQuestionId entry to uuid_ops so the index matches
the column types.
| index('quiz_questions_quiz_display_order_idx').using( | ||
| 'btree', | ||
| table.quizId.asc().nullsLast().op('int4_ops'), | ||
| table.displayOrder.asc().nullsLast().op('int4_ops') | ||
| ), |
There was a problem hiding this comment.
Incorrect operator class for UUID column in index.
quizId is a uuid column but uses int4_ops. Should be uuid_ops.
🔎 Proposed fix
index('quiz_questions_quiz_display_order_idx').using(
'btree',
- table.quizId.asc().nullsLast().op('int4_ops'),
+ table.quizId.asc().nullsLast().op('uuid_ops'),
table.displayOrder.asc().nullsLast().op('int4_ops')
),📝 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.
| index('quiz_questions_quiz_display_order_idx').using( | |
| 'btree', | |
| table.quizId.asc().nullsLast().op('int4_ops'), | |
| table.displayOrder.asc().nullsLast().op('int4_ops') | |
| ), | |
| index('quiz_questions_quiz_display_order_idx').using( | |
| 'btree', | |
| table.quizId.asc().nullsLast().op('uuid_ops'), | |
| table.displayOrder.asc().nullsLast().op('int4_ops') | |
| ), |
🤖 Prompt for AI Agents
In frontend/drizzle/schema.ts around lines 84 to 88, the index definition uses
the wrong operator class for the uuid column quizId (currently int4_ops); change
the operator class for table.quizId to 'uuid_ops' while keeping
table.displayOrder's operator class (int4_ops) intact so the index becomes
quizId.asc().nullsLast().op('uuid_ops') and
displayOrder.asc().nullsLast().op('int4_ops').
| index('quiz_attempts_quiz_percentage_completed_at_idx').using( | ||
| 'btree', | ||
| table.quizId.asc().nullsLast().op('numeric_ops'), | ||
| table.percentage.asc().nullsLast().op('numeric_ops'), | ||
| table.completedAt.asc().nullsLast().op('uuid_ops') | ||
| ), | ||
| index('quiz_attempts_user_completed_at_idx').using( | ||
| 'btree', | ||
| table.userId.asc().nullsLast().op('text_ops'), | ||
| table.completedAt.asc().nullsLast().op('text_ops') | ||
| ), |
There was a problem hiding this comment.
Multiple incorrect operator classes in index definition.
- Line 184:
quizId(uuid) usesnumeric_ops— should beuuid_ops - Line 186:
completedAt(timestamp) usesuuid_ops— should betimestamptz_opsor omitted (btree default works)
🔎 Proposed fix
index('quiz_attempts_quiz_percentage_completed_at_idx').using(
'btree',
- table.quizId.asc().nullsLast().op('numeric_ops'),
+ table.quizId.asc().nullsLast().op('uuid_ops'),
table.percentage.asc().nullsLast().op('numeric_ops'),
- table.completedAt.asc().nullsLast().op('uuid_ops')
+ table.completedAt.asc().nullsLast().op('timestamptz_ops')
),🤖 Prompt for AI Agents
In frontend/drizzle/schema.ts around lines 182 to 192, the index operator
classes are incorrect: change table.quizId.op('numeric_ops') to
table.quizId.op('uuid_ops'), change table.completedAt.op('uuid_ops') to
table.completedAt.op('timestamptz_ops') (or remove the op to use btree default)
in the first index, and in the second index change
table.completedAt.op('text_ops') to table.completedAt.op('timestamptz_ops') (or
remove the op) while leaving table.userId.op('text_ops') as-is.
feat(q&a): localized questions, new schema and seeding
Q&A Localization Support
Overview
This update introduces full localization support for Q&A content and improves the database structure for questions and categories.
Key Changes
Database & Schema
questionsinto a dedicatedquestions.tsschemauuididentifiersslugandlocalefields for proper localizationname→titlefor clearer semanticsSeeding & Data
seed-questions.tspackage.jsoncommand:en,pl,uk)slugifyutility for consistent slugsAPI & Frontend
category + localeproxy.tsTabsSection.tsxto:/uk/uk/q&a)Testing Instructions
Run database seeds:
Open Q&A page in different locales:
/uk/q&a/en/q&a/pl/q&aSwitch language using the language toggle
Verify that questions update correctly per locale
Results
Summary by CodeRabbit
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.