Conversation
WalkthroughThis PR migrates the backend from Django REST Framework serializers to Pydantic models, adds backward-compatibility fields to domain models, and replaces the frontend's manual API client with an auto-generated OpenAPI TypeScript client. The changes centralize API contract management and modernize the codebase's type safety. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Areas requiring extra attention:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
frontend/src/pages/InterviewQuestions.tsx (1)
149-156: Same error-casting considerations as in CompareCandidatesThis mirrors
(error as Error).messagefromCompareCandidates.tsx. The approach is fine if your mutation errors are alwaysError-like; otherwise, consider a small fallback (e.g.,instanceof Error ? error.message : String(error)) to avoid an empty message.frontend/src/pages/ExplainMatch.tsx (1)
96-103: Consistent Error-based message rendering; same optional fallback noteYou’re consistently using
(error as Error).messagehere as well. That keeps error handling uniform across pages; just be aware that non-Error values will render as an empty span, so a tiny fallback toString(error)would improve robustness if that’s a possibility.
🧹 Nitpick comments (19)
frontend/src/pages/CompareCandidates.tsx (1)
148-155: Error casting assumes Error-like objects; consider a fallback messageUsing
(error as Error).messageis fine if all your mutation errors are realError(orError-like) instances. If anything else can reachmutation.error, the span will render empty. You may want a small fallback such aserror instanceof Error ? error.message : String(error)to avoid a blank error in edge cases, but not strictly required.frontend/tsconfig.tsbuildinfo (1)
1-1: Consider removing this build artifact from version control
tsconfig.tsbuildinfois an incremental compile artifact and usually not checked in, since it changes frequently per-developer and per-build. Recommend deleting it from the repo and adding it to.gitignoreto avoid noisy diffs.frontend/src/components/filters/EducationFilter.tsx (1)
25-42: Tighten StatusesEnum handling and normalize “Any status” comparisonsNice move wiring
EducationRequirement.statusesup toStatusesEnum[]; a couple of small refinements could improve robustness:
- Avoid blind casting to
StatusesEnum[]const typedStatuses = statuses as StatusesEnum[];relies purely on trust. If
levelOption.statusesever contains an unexpected string, you’ll silently write an invalid enum value. Consider a narrowing helper instead:const allowed: StatusesEnum[] = ['completed', 'ongoing', 'incomplete']; const typedStatuses = statuses.filter((s): s is StatusesEnum => allowed.includes(s as StatusesEnum) );and then keep using
typedStatuseseverywhere.
- Normalize status arrays when checking for “same selection”
The comparison
const sameStatusesSelected = JSON.stringify(existing.statuses?.sort()) === JSON.stringify(statuses.sort());treats
existing.statuses === nulldifferently fromstatuses.length === 0, which makes it impossible to clear a level by re‑selecting “Any Status”. Normalizing both sides to arrays (and reusingtypedStatuses) avoids this:const normalize = (s: StatusesEnum[] | null | undefined) => (s ?? []).slice().sort(); const sameStatusesSelected = JSON.stringify(normalize(existing?.statuses)) === JSON.stringify(normalize(typedStatuses));This keeps your internal state aligned with the enum while making the toggle behavior more intuitive.
Also applies to: 95-108
docker-compose.yaml (1)
171-176: Ensurewgetis available in the frontend image; consider a healthcheck-based alternativeThe startup sequence—waiting for
/api/schema/, runninggenerate:api, thennpm run dev—is a nice way to keep the generated client in sync. One thing to confirm is that your frontend image actually haswgetinstalled; if not, thissh -cwill fail before the app starts.If you run into that, either install
wget(or switch to a tool already present in the base image), or move the readiness logic into a backend healthcheck and gatefrontendwithdepends_on: backend: condition: service_healthyinstead of an inline loop.backend/core/domain/resume.py (2)
314-316: Same concern: potential confusion with legacy field names.
namelikely maps tolanguage.nameandleveltocefrorself_assessed. Without a validator to map these legacy fields to their canonical counterparts, consumers may be unsure which to use, and data could be inconsistent.Consider adding mapping logic in
accept_legacy_language:@model_validator(mode="before") @classmethod def accept_legacy_language(cls, v: dict): if isinstance(v, dict) and isinstance(v.get("language"), str): v["language"] = {"name": v["language"]} + # Map legacy 'name' to language.name + if isinstance(v, dict) and "name" in v and "language" not in v: + v["language"] = {"name": v.pop("name")} + # Map legacy 'level' to cefr if not present + if isinstance(v, dict) and "level" in v and "cefr" not in v: + v["cefr"] = v.get("level") return v
334-337: Consider adding a legacy field mapper for Award.The
title/issuer/datefields appear to be alternatives forname/organization/year. For consistency with other models that haveaccept_legacy_*validators, consider adding similar mapping logic.frontend/src/services/fileConfigService.ts (1)
1-18: Tighten data/error handling around v1ConfigFileTypesRetrieveThe basic wiring to
v1ConfigFileTypesRetrievelooks fine, but you might want to harden this a bit:
- Guard against the (unlikely but possible) case where
datais falsy but noerroris set.- Prefer extracting a more informative message than
String(error)if the SDK exposes one (e.g.,error.messageor adetailfield).For example:
export async function getFileConfig(): Promise<FileConfigResponse> { const { data, error } = await v1ConfigFileTypesRetrieve(); if (error) { const message = error instanceof Error ? error.message : typeof error === 'string' ? error : JSON.stringify(error); throw new Error(message); } if (!data) { throw new Error('Empty response from file config endpoint'); } return data as FileConfigResponse; }frontend/src/pages/Upload.tsx (1)
60-97: Ensure mutation error is typed/narrowed to something with.messageThe new UI logic assumes
errorhas amessagestring and callserror.message.includes(...). That’s fine at runtime ifuploadResumethrows standardErrorinstances, but TypeScript will treaterrorasunknownunlessuseResumeUploadexplicitly setsTError = Error.To keep both TS and runtime happy, consider narrowing
errorbefore using.message:{error && ( <div /* ... */> {/* ... */} <div /* title styles */> {error instanceof Error ? error.message : 'Upload failed'} </div> {error instanceof Error && error.message && ( <div /* explanation styles */> {error.message.includes('email') ? ( <>Make sure your resume contains a valid email address. The system uses email to identify and deduplicate resumes.</> ) : error.message.includes('already exists') ? ( <>A resume with this email has already been uploaded. You can search for it or delete it first.</> ) : ( <>Please check that your file is a valid resume document (PDF, DOCX, DOC, JPG, PNG).</> )} </div> )} </div> )}Alternatively (or additionally), you can type
useResumeUploadasuseMutation<ResumeResponse, Error, File>soerroris statically known to beError.frontend/src/services/healthService.ts (1)
1-31: Health fetch wiring is good; consider mirroring stronger guardsThe switch to
v1HealthRetrieveis correct and aligned with the new client. For robustness, you might want to mirror the stronger pattern suggested forgetFileConfig:
- Derive a clearer error message than
String(error).- Assert
datais present before casting/returning to avoid silently returningundefined as HealthData.Not strictly required, but it will make failures easier to debug and safer if the SDK behavior changes.
backend/processor/workers/processing.py (1)
89-99: Pydantic dump is appropriate; considermode="json"if serialized directlyUsing
result.model_dump()is a nice cleanup versus manual dict construction and keeps serialization aligned with the Pydantic model.If
result_dictis ultimately stored/serialized as JSON (e.g., into a JSONField or message payload) and includes non-primitive types likedatetime, you may want to switch to:result_dict = result.model_dump(mode="json")to guarantee JSON-friendly output. If downstream code already handles Python-native types correctly, the current code is fine.
frontend/src/pages/Search.tsx (1)
7-8: SearchFiltersSchema wiring looks good; a couple of minor refinements to consider
Type alignment: Importing and using
SearchFiltersSchemafrom the generated client forfilterskeeps the UI in lockstep with the backend’sSearchFiltersSchemaserializer. That’s a solid move.Active filter counting: The generic
Object.values(filters)+lengthheuristic works across arrays and strings and will correctly treat non-empty values as active. One nuance: ayears_experienceof0will be counted as an active filter even though it behaves like “no minimum”. If you’d prefer not to count that, you could special-case numeric fields:const activeFilterCount = Object.entries(filters).filter(([key, v]) => { if (v == null) return false; if (Array.isArray(v) || typeof v === 'string') return v.length > 0; if (typeof v === 'number' && key === 'years_experience') return v > 0; return true; }).length;Execution time display: The cast for
execution_timeis safe and the<1sfallback is reasonable. If you later formalize this field in the SDK type, you can drop the cast and non-null assertion.Also applies to: 17-18, 65-71, 89-90
frontend/src/pages/AIReview.tsx (1)
204-209: Duck-typing check for SectionFeedback is reasonable.The runtime check for
must,should, oradviseproperties effectively filters toSectionFeedbackobjects. This is a pragmatic approach when iterating over dynamic object entries.Consider adding a type guard function for better reusability if this pattern is needed elsewhere:
function isSectionFeedback(value: unknown): value is SectionFeedback { const v = value as SectionFeedback; return v?.must !== undefined || v?.should !== undefined || v?.advise !== undefined; }backend/processor/views.py (1)
166-166: Consider usingRequesttype instead ofAny.The
Anytype annotation loses type information. Since this is a sync method (not async), and other sync views might use the standard DRFRequest, consider keeping the type specific:- def get(self, request: Any) -> Response: + def get(self, request: Request) -> Response:backend/core/domain/processing.py (1)
26-31: Consider makingresumeoptional or providing a mechanism for partial results.The
resumefield is required with no default, meaning aProcessingResultcannot be instantiated without a fully parsedResume. If processing fails mid-way, this could complicate error handling. Consider whetherresume: Resume | None = Nonewould better support partial failure states.frontend/src/services/resumeService.ts (2)
16-31: Inconsistent API call pattern:uploadResumeuses rawfetchwhile other functions use the generated SDK.This creates a maintenance burden—if the API base URL or authentication changes, this function must be updated separately. Consider using the generated SDK's upload function if available, or at minimum, document why raw
fetchis necessary here (e.g., multipart/form-data handling).
33-37: Error handling loses type information.
String(error)coerces the error to a string, losing structured error details that may be useful for debugging or user feedback. Consider preserving the error structure:export async function getResumeStatus(uid: string): Promise<ResumeResponse> { const { data, error } = await v1ResumesRetrieve2({ path: { uid } }); - if (error) throw new Error(String(error)); + if (error) throw error; return data as ResumeResponse; }Or extract meaningful details if the error is an object with
messageordetailfields.frontend/src/pages/JobStatus.tsx (3)
269-281: Move interface definitions outside the component.
PersonalDataandSectionDataare defined inside theJobStatuscomponent. This causes them to be re-created on every render (though the runtime cost is negligible) and prevents reuse. Move them to module scope for clarity.
283-289: TheisEmptycheck has the same fragile array detection issue.Lines 287-288 repeat the problematic array check pattern. Use
Array.isArray()for clarity:- const asArray = data as unknown[]; - const asRecord = data as Record<string, unknown>; const isEmpty = !data || - (asArray.length !== undefined && asArray.length === 0) || - (asRecord && Object.keys(asRecord).length === 0 && asArray.length === undefined); + (Array.isArray(data) && data.length === 0) || + (!Array.isArray(data) && typeof data === 'object' && Object.keys(data).length === 0);
2028-2037: SameisEmptylogic concern applies here.This duplicates the fragile array detection logic from
renderResumeSection. Consider extracting a sharedisEmptyutility function that usesArray.isArray()for consistency and maintainability.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (5)
frontend/package-lock.jsonis excluded by!**/package-lock.jsonfrontend/src/api/generated/index.tsis excluded by!**/generated/**frontend/src/api/generated/sdk.gen.tsis excluded by!**/generated/**frontend/src/api/generated/types.gen.tsis excluded by!**/generated/**package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (35)
backend/core/domain/processing.py(2 hunks)backend/core/domain/resume.py(6 hunks)backend/processor/serializers.py(0 hunks)backend/processor/views.py(5 hunks)backend/processor/workers/processing.py(1 hunks)backend/search/serializers.py(2 hunks)docker-compose.yaml(1 hunks)frontend/openapi-ts.config.ts(1 hunks)frontend/package.json(2 hunks)frontend/src/api/client.ts(1 hunks)frontend/src/components/ResultCard.tsx(1 hunks)frontend/src/components/SearchFilters.tsx(2 hunks)frontend/src/components/filters/ActiveFiltersBar.tsx(1 hunks)frontend/src/components/filters/EducationFilter.tsx(4 hunks)frontend/src/components/filters/LanguagesFilter.tsx(1 hunks)frontend/src/components/filters/LocationsFilter.tsx(1 hunks)frontend/src/hooks/useResumeSearch.ts(2 hunks)frontend/src/hooks/useResumeUpload.ts(1 hunks)frontend/src/lib/api.ts(0 hunks)frontend/src/pages/AIReview.tsx(5 hunks)frontend/src/pages/CompareCandidates.tsx(1 hunks)frontend/src/pages/Error.tsx(1 hunks)frontend/src/pages/ExplainMatch.tsx(1 hunks)frontend/src/pages/Health.tsx(1 hunks)frontend/src/pages/InterviewQuestions.tsx(1 hunks)frontend/src/pages/JobStatus.tsx(16 hunks)frontend/src/pages/Landing.tsx(3 hunks)frontend/src/pages/PrivacyPolicy.tsx(1 hunks)frontend/src/pages/Search.tsx(4 hunks)frontend/src/pages/Upload.tsx(1 hunks)frontend/src/services/fileConfigService.ts(2 hunks)frontend/src/services/healthService.ts(2 hunks)frontend/src/services/ragService.ts(3 hunks)frontend/src/services/resumeService.ts(1 hunks)frontend/tsconfig.tsbuildinfo(1 hunks)
💤 Files with no reviewable changes (2)
- frontend/src/lib/api.ts
- backend/processor/serializers.py
🧰 Additional context used
🧬 Code graph analysis (16)
frontend/src/pages/InterviewQuestions.tsx (1)
frontend/src/pages/Error.tsx (1)
Error(66-124)
frontend/src/components/filters/ActiveFiltersBar.tsx (2)
backend/search/serializers.py (1)
SearchFiltersSchema(117-160)frontend/src/api/generated/types.gen.ts (1)
SearchFiltersSchema(559-588)
frontend/src/components/SearchFilters.tsx (2)
backend/search/serializers.py (1)
SearchFiltersSchema(117-160)frontend/src/api/generated/types.gen.ts (1)
SearchFiltersSchema(559-588)
frontend/src/services/fileConfigService.ts (1)
frontend/src/api/generated/sdk.gen.ts (1)
v1ConfigFileTypesRetrieve(11-16)
frontend/src/pages/Search.tsx (2)
backend/search/serializers.py (1)
SearchFiltersSchema(117-160)frontend/src/api/generated/types.gen.ts (1)
SearchFiltersSchema(559-588)
frontend/src/api/client.ts (1)
frontend/src/api/generated/sdk.gen.ts (1)
client(6-6)
frontend/src/hooks/useResumeSearch.ts (2)
backend/search/serializers.py (3)
VectorSearchQuerySchema(163-188)GraphSearchQuerySchema(191-205)HybridSearchQuerySchema(208-230)frontend/src/api/generated/types.gen.ts (3)
VectorSearchQuerySchema(707-728)GraphSearchQuerySchema(235-248)HybridSearchQuerySchema(250-267)
frontend/src/components/filters/EducationFilter.tsx (1)
frontend/src/api/generated/types.gen.ts (1)
StatusesEnum(705-705)
frontend/src/pages/CompareCandidates.tsx (1)
frontend/src/pages/Error.tsx (1)
Error(66-124)
frontend/src/pages/ExplainMatch.tsx (1)
frontend/src/pages/Error.tsx (1)
Error(66-124)
backend/processor/views.py (3)
backend/core/domain/processing.py (1)
ResumeResponse(34-43)frontend/src/api/generated/types.gen.ts (2)
ResumeResponse(511-528)ResumeListResponse(489-506)backend/processor/serializers.py (1)
FileUploadSerializer(24-65)
frontend/src/services/resumeService.ts (4)
backend/core/domain/processing.py (1)
ResumeResponse(34-43)frontend/src/api/generated/types.gen.ts (1)
ResumeResponse(511-528)frontend/src/api/client.ts (1)
API_BASE_URL(4-4)frontend/src/api/generated/sdk.gen.ts (5)
v1ResumesRetrieve2(91-96)v1SearchSemanticCreate(131-136)v1SearchStructuredCreate(141-146)v1SearchHybridCreate(121-126)v1FiltersRetrieve(21-26)
frontend/src/services/healthService.ts (1)
frontend/src/api/generated/sdk.gen.ts (1)
v1HealthRetrieve(31-36)
backend/core/domain/resume.py (2)
frontend/src/api/generated/types.gen.ts (2)
EducationStatus(167-167)Location(370-374)backend/core/models/neo4j_models.py (1)
EducationStatus(28-31)
frontend/src/services/ragService.ts (2)
frontend/src/api/generated/types.gen.ts (3)
ExplainMatchRequest(188-191)CompareCandidatesRequest(30-34)InterviewQuestionsRequest(273-278)frontend/src/api/generated/sdk.gen.ts (3)
v1RagExplainMatchCreate(51-56)v1RagCompareCreate(41-46)v1RagInterviewQuestionsCreate(61-66)
backend/core/domain/processing.py (2)
backend/core/domain/resume.py (1)
Resume(362-414)frontend/src/api/generated/types.gen.ts (5)
Resume(463-484)ReviewResult(530-544)ProcessingMetadata(425-437)ProcessingResult(442-446)ResumeResponse(511-528)
🪛 Biome (2.1.2)
frontend/src/pages/Error.tsx
[error] 66-66: Do not shadow the global "Error" property.
Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.
(lint/suspicious/noShadowRestrictedNames)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (36)
frontend/package.json (1)
6-12: OpenAPI client tooling wiring looks consistent with the new workflowThe
generate:apiscript and the@hey-api/*dependencies line up withopenapi-ts.config.tsand the docker-compose startup flow. This should give you a clean, reproducible client surface as long asopenapi-tspicks up the config correctly.Please confirm locally that
npm run generate:apisucceeds (using your backend’s schema URL) and regeneratessrc/api/generatedas expected in both Docker and non-Docker dev environments.Also applies to: 14-15, 27-27
frontend/openapi-ts.config.ts (1)
1-17: OpenAPI config matches the new client toolchainThe config is coherent with the new scripts and Docker flow:
OPENAPI_URLoverride for containers, localhost default for local dev, and@hey-api/client-fetch+ types/sdk plugins targetingsrc/api/generated. This should be a solid base for codegen.Please double-check that:
- Running
openapi-ts(vianpm run generate:api) picks up this config file, and- The generated client compiles cleanly against your current backend schema (no missing/renamed fields).
backend/search/serializers.py (2)
34-62: Well-structured serializers for search results.The new
EducationEntrySerializer,LanguageEntrySerializer, andLocationEntrySerializerproperly encapsulate nested data structures with appropriate field types and help text. This improves API contract clarity compared to the previousDictField/ListFieldapproach.
75-83: LGTM!The integration of the new serializers into
SearchResultSerializeris correct. Themany=Trueflag is appropriately used for list fields (education,languages), and all fields are properly marked as optional/nullable for search result flexibility.backend/core/domain/resume.py (4)
66-69: Potential data inconsistency between canonical and legacy fields.The
Contactmodel now hasgithub,websiteas direct fields alongsidelinks.linkedin,links.github. This creates two sources of truth for the same data. If both are populated with different values, it's unclear which takes precedence.Consider adding a
model_validatorto either:
- Sync legacy fields from nested
linkson read- Merge/prefer one source when both are provided
# Example sync validator approach: @model_validator(mode="after") def sync_legacy_links(self) -> "Contact": if self.links: if self.linkedin is None and self.links.linkedin: self.linkedin = self.links.linkedin if self.github is None and self.links.github: self.github = self.links.github return self
202-202: LGTM!Adding an optional
descriptionfield toProjectis a reasonable enhancement for providing brief overviews alongside the existingkey_points.
239-241: LGTM!The
yearfield provides a simpler alternative tostart/enddates for graduation year, andgpaas a string type appropriately handles various formats (numeric, fractional, or text-based grading systems).
375-383: Populate legacy flat fields from canonical nested sources, or document their non-population.The Resume model has legacy flat fields (
name,phone,location,github,website,resume_lang) that duplicate data frompersonal_infoand its nested structures. The@model_validatorat line 385 handles backward-compatibility conversions for incoming data but does not auto-populate these flat fields from the canonical nested sources. They remainNoneunless explicitly set in the input data.Either:
- Add validator logic to sync
personal_info→ legacy fields after initialization, or- Remove these fields if they're genuinely unused, or
- Clearly document that these fields are not auto-synced and explain when/why callers should populate them.
This prevents inconsistency where
personal_info.namehas a value butnameisNone.frontend/src/components/ResultCard.tsx (1)
3-3: LGTM!The import path update aligns with the broader migration to the auto-generated OpenAPI TypeScript client.
frontend/src/components/filters/LocationsFilter.tsx (1)
2-2: LGTM!Import path updated to use the new API client module, consistent with other components in this migration.
frontend/src/pages/Health.tsx (1)
2-2: LGTM!Import path updated for
API_BASE_URLto align with the centralized API client module.frontend/src/hooks/useResumeUpload.ts (1)
2-7: Import cleanup and hook wiring look correct
useResumeUploadstill correctly forwards touploadResumeviauseMutation, and the remaining import is the right runtime dependency. No issues here.frontend/src/components/filters/LanguagesFilter.tsx (1)
3-53: LanguageRequirement import source aligns with new API clientSwitching
LanguageRequirementto come from../../api/clientmatches the new centralized API types, and the component’s usage (language,min_cefr) remains consistent with the filter schema. Looks good.frontend/src/pages/PrivacyPolicy.tsx (1)
4-52: API_BASE_URL import swap is consistent and non-breakingUsing
API_BASE_URLfrom../api/clientkeeps this page aligned with the new centralized API client. The docs link construction remains the same; no behavior change.frontend/src/api/client.ts (1)
1-15: Clean API client configuration pattern.The setup correctly configures the generated client singleton with the base URL from environment variables and provides a centralized export point. The barrel export (
export * from './generated') ensures consumers only need to import from../api/client.frontend/src/components/SearchFilters.tsx (1)
2-14: Type migration to generated schema looks correct.The
SearchFiltersSchemafrom the generated API client matches the expected shape, and the component's usage of optional fields with nullish coalescing (??) is appropriate for the nullable schema properties.frontend/src/pages/AIReview.tsx (1)
48-48: Type assertion is appropriate given the status guard.The cast to
ProcessingResultis safe here since it's guarded by thejob?.status === "completed"condition, which guarantees the result field is populated per the API contract.frontend/src/pages/Landing.tsx (2)
3-3: Import path updated to new API client module.
41-80: Good addition of explicit Feature typing.Defining the
Featureinterface improves type safety and documentation for the layout flags. The typed array allows the bento grid to conditionally apply CSS classes based onwide,tall, andsmallproperties.frontend/src/components/filters/ActiveFiltersBar.tsx (1)
1-5: Type migration consistent with SearchFilters component.The import change and prop type update align with the centralized API client types. The component's field access patterns are compatible with the nullable schema properties.
frontend/src/hooks/useResumeSearch.ts (1)
8-25: Type migration to generated schemas is correct.The
UnifiedSearchParamsdiscriminated union using the new schema types maintains the same structure. The intersection with{ type: "semantic" | "structured" | "hybrid" }provides proper type narrowing in the mutation function.backend/processor/views.py (2)
27-34: Clean Pydantic model for paginated response schema.The
ResumeListResponsemodel properly documents the pagination structure for OpenAPI generation, matching the frontend'sResumeListResponsetype fromtypes.gen.ts.
47-47: OpenAPI schema references updated to Pydantic models.Using
ResumeListResponseandResumeResponsePydantic models inextend_schemaresponses enables drf-spectacular to generate accurate TypeScript types for the frontend client.Also applies to: 70-70, 93-93
frontend/src/pages/Error.tsx (2)
5-27: Well-structured error state normalization.The
ErrorStateinterface handles various error shapes from different sources (axios-style withresponse, fetch-style withstatus/statusCode, etc.), andbuildErrorDetailsconsolidates them into a consistent format for display.
29-38: Consolidating error parsing improves maintainability.The
buildErrorDetailsfunction with fallback chains (error.status ?? error.statusCode,error.data ?? error.response) handles the variety of error formats encountered in practice.backend/core/domain/processing.py (4)
1-4: LGTM!Clean imports using modern Python typing features and Pydantic for model definitions.
10-23: LGTM!ProcessingMetadata is well-structured with sensible defaults for all fields. The optional error fields appropriately use
str | Nonefor cases where errors may or may not occur.
34-43: LGTM!
ResumeResponsealigns well with the generated TypeScript type (frontend/src/api/generated/types.gen.tslines 510-527). TheLiteraltype for status provides good type safety, and Field descriptions will generate useful OpenAPI documentation.
46-49: LGTM!
EmbeddingTextDataas a typedNamedTupleis appropriate for this simple data structure.frontend/src/services/resumeService.ts (2)
1-14: LGTM!Clean import structure from the centralized API client, properly importing both types and SDK functions.
39-61: LGTM with a note on type assertions.The search functions and
getFilterOptionsfollow a consistent pattern. The type assertions (data as SearchResponse,data as FilterOptions) are acceptable given the SDK's typing, but verify that the generated types match the actual API responses to avoid runtime surprises.frontend/src/services/ragService.ts (3)
1-8: LGTM!Clean imports from the centralized API client, using the generated SDK functions and request types.
92-108: LGTM with same error handling note asresumeService.ts.The three RAG functions follow the established pattern. The same
String(error)concern applies here—consider preserving error structure for better debugging.
10-90: Verify whether generated types exist in the project's OpenAPI infrastructure.These response interfaces (
JobMatchExplanation,CandidateComparison,InterviewQuestionSet, etc.) appear to duplicate types that should be auto-generated from the OpenAPI spec. Using generated types ensures frontend-backend alignment and reduces maintenance burden when the API changes.If the generated types are incomplete or incorrect, fix the OpenAPI spec rather than maintaining parallel definitions. If no generated types infrastructure exists or the types are not available through code generation, maintaining local interface definitions is the appropriate approach.
frontend/src/pages/JobStatus.tsx (2)
4-5: LGTM!Good migration to import types from the centralized API client, ensuring type alignment with the generated OpenAPI types.
1355-1358: LGTM!The error casting
(error as Error).messageis a reasonable approach given the hook's return type.
| ${dataBlock}${stackBlock}`; | ||
| } | ||
|
|
||
| export default function Error() { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find usages of the Error component to verify route/import naming
echo "=== Searching for imports of Error component ==="
rg -n "from.*Error" --type=ts --type=tsx -g '!Error.tsx' frontend/ 2>/dev/null | head -20
echo ""
echo "=== Searching for import statements ==="
rg -n "import.*Error" --type=ts --type=tsx -g '!Error.tsx' frontend/ 2>/dev/null | head -20
echo ""
echo "=== Checking Error.tsx for usage of global Error ==="
cat -n frontend/src/pages/Error.tsxRepository: HardMax71/ResuMariner
Length of output: 4549
🏁 Script executed:
#!/bin/bash
# Look for router configuration
echo "=== Searching for router configuration ==="
find frontend/src -name "*route*" -o -name "*router*" -o -name "*App*" | head -10
echo ""
echo "=== Check App files ==="
find frontend/src -maxdepth 2 -name "App.*" | xargs cat -n 2>/dev/null
echo ""
echo "=== Check for any Error reference in config files ==="
rg -n "Error" frontend/src --type=ts --type=tsx -B2 -A2 | grep -E "(route|Route|import|Error)" | head -30Repository: HardMax71/ResuMariner
Length of output: 2446
Rename component to avoid shadowing global Error.
The component name Error shadows the global Error constructor. While this module doesn't directly use the native Error class, the shadowing can cause confusion and potential issues in future maintenance. Rename the component to ErrorPage.
Update the import and route reference in frontend/src/App.tsx (lines 11 and 38).
🧰 Tools
🪛 Biome (2.1.2)
[error] 66-66: Do not shadow the global "Error" property.
Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.
(lint/suspicious/noShadowRestrictedNames)
🤖 Prompt for AI Agents
In frontend/src/pages/Error.tsx around line 66 the React component is named
Error which shadows the global Error constructor; rename the component to
ErrorPage (update the component declaration and any internal references/exports
in this file) and then update the import and route usage in frontend/src/App.tsx
(change the import at line 11 to import ErrorPage and update the route reference
at line 38 to use ErrorPage) so no global name is shadowed.
| const renderValue = (value: unknown): string => { | ||
| if (value === null || value === undefined) return "—"; | ||
| if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { | ||
| return String(value); | ||
|
|
||
| // Primitives - use String() which handles all primitives | ||
| const primitiveResult = String(value); | ||
| if (primitiveResult !== "[object Object]" && !primitiveResult.startsWith("[object")) { | ||
| return primitiveResult; | ||
| } | ||
| if (typeof value === "object") { | ||
| // Handle common nested structures | ||
| if (value.city || value.country) return [value.city, value.country].filter(Boolean).join(", "); | ||
| if (value.name) return value.name; | ||
| if (value.title) return value.title; | ||
| if (value.value) return value.value; | ||
| // For arrays, join them | ||
| if (Array.isArray(value)) return value.map(renderValue).join(", "); | ||
| // Otherwise stringify | ||
| return JSON.stringify(value); | ||
|
|
||
| // Object handling via type assertion | ||
| const obj = value as RenderableObject | unknown[]; | ||
|
|
||
| // Array handling - check for length property and numeric indexing | ||
| const asArray = obj as unknown[]; | ||
| if (asArray.length !== undefined && asArray[0] !== undefined || (obj as unknown[]).length === 0) { | ||
| return (obj as unknown[]).map(renderValue).join(", "); | ||
| } | ||
| return String(value); | ||
|
|
||
| // Object with known properties | ||
| const asObj = obj as RenderableObject; | ||
| if (asObj.city || asObj.country) return [asObj.city, asObj.country].filter(Boolean).join(", "); | ||
| if (asObj.name) return asObj.name; | ||
| if (asObj.title) return asObj.title; | ||
| if (asObj.text) return asObj.text; | ||
| if (asObj.value) return asObj.value; | ||
|
|
||
| // Fallback to JSON | ||
| return JSON.stringify(value); | ||
| }; |
There was a problem hiding this comment.
Fragile array detection logic.
The array check on line 41 is error-prone:
if (asArray.length !== undefined && asArray[0] !== undefined || (obj as unknown[]).length === 0)This condition has operator precedence issues (&& binds tighter than ||) and checking asArray[0] !== undefined incorrectly excludes arrays like [undefined, 1, 2]. Consider using Array.isArray():
- const asArray = obj as unknown[];
- if (asArray.length !== undefined && asArray[0] !== undefined || (obj as unknown[]).length === 0) {
- return (obj as unknown[]).map(renderValue).join(", ");
+ if (Array.isArray(obj)) {
+ return obj.map(renderValue).join(", ");
}🤖 Prompt for AI Agents
In frontend/src/pages/JobStatus.tsx around lines 27 to 55, the array detection
logic is fragile and wrong due to operator precedence and checking asArray[0]
which excludes valid arrays with undefined first elements; replace that block
with a proper Array.isArray(value) guard (or Array.isArray(obj)) to detect
arrays, handle empty arrays explicitly, and map/renderValue over array elements
then join with ", " — remove the current length/element checks and use the
Array.isArray type guard so arrays (including ones with undefined entries) are
correctly processed.
Summary by CodeRabbit
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.