feat(api): implement TypeORM REST endpoints, 3-tier architecture, and… - #2
Merged
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Multiple confirmed security/data-exposure issues exist around projects access control and unverified header-based authentication, plus input validation gaps that can cause runtime errors.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Implements a new REST API surface for core domain objects (categories, products, projects) using a 3-tier routing/controller/service architecture over TypeORM repositories, and adds global middleware for rate limiting and centralized error handling.
Changes:
- Added controllers, services, and routes for
/categories,/products, and/projects. - Implemented product listing/search with filtering, sorting, and pagination via repository methods (
findAndCount,ILike,In,Between, etc.). - Added global middleware: IP rate limiting and centralized JSON error handling; updated migration rollback to use repositories.
File summaries
| File | Description |
|---|---|
| api/src/services/projects.service.ts | Implements CRUD logic for projects using TypeORM repositories. |
| api/src/services/products.service.ts | Implements product search/listing and identifier lookup with repository operators. |
| api/src/services/categories.service.ts | Implements category listing and slug lookup. |
| api/src/routes/projects.routes.ts | Registers /projects CRUD routes. |
| api/src/routes/products.routes.ts | Registers /products list and identifier routes. |
| api/src/routes/index.ts | Aggregates category/product/project routers under a single router. |
| api/src/routes/categories.routes.ts | Registers /categories list and slug routes. |
| api/src/migrations/1740441600000-SeedInitialData.ts | Updates seed migration rollback to clear via repositories. |
| api/src/middlewares/rateLimiter.ts | Adds a global 60 req/min IP-based rate limiter. |
| api/src/middlewares/errorHandler.ts | Adds centralized JSON error handling middleware. |
| api/src/middlewares/auth.ts | Adds optional/required auth middleware based on request headers. |
| api/src/index.ts | Mounts routes, rate limiter, and error handler; updates startup logging and paths. |
| api/src/controllers/projects.controller.ts | Adds request/response handling for project endpoints. |
| api/src/controllers/products.controller.ts | Adds request/response handling for product endpoints and query parsing. |
| api/src/controllers/categories.controller.ts | Adds request/response handling for category endpoints. |
| api/package.json | Adds express-rate-limit dependency. |
| api/package-lock.json | Locks express-rate-limit (and updates metadata). |
| .env.example | Adds Neon Auth-related environment variable examples. |
Review details
Files not reviewed (1)
- api/package-lock.json: Generated file
- Files reviewed: 17/18 changed files
- Comments generated: 10
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+6
to
+10
| router.get('/', (req, res, next) => projectsController.getProjects(req, res, next)); | ||
| router.get('/:id', (req, res, next) => projectsController.getProjectById(req, res, next)); | ||
| router.post('/', (req, res, next) => projectsController.createProject(req, res, next)); | ||
| router.put('/:id', (req, res, next) => projectsController.updateProject(req, res, next)); | ||
| router.delete('/:id', (req, res, next) => projectsController.deleteProject(req, res, next)); |
Comment on lines
+5
to
+9
| async getProjects(req: Request, res: Response, next: NextFunction) { | ||
| try { | ||
| const userId = typeof req.query.userId === 'string' ? req.query.userId : undefined; | ||
| const projects = await projectsService.getProjects(userId); | ||
|
|
Comment on lines
+20
to
+27
| async getProjects(userId?: string) { | ||
| const where: FindOptionsWhere<Project> = userId ? { user: { id: userId } } : {}; | ||
|
|
||
| return await this.projectRepo.find({ | ||
| where, | ||
| relations: { user: true }, | ||
| order: { updatedAt: 'DESC' }, | ||
| }); |
Comment on lines
+5
to
+9
| export interface CreateProjectInput { | ||
| title: string; | ||
| userId?: string; | ||
| data?: Record<string, any>; | ||
| } |
Comment on lines
+53
to
+58
| if (params.manufacturer) { | ||
| const manufacturers = Array.isArray(params.manufacturer) | ||
| ? params.manufacturer | ||
| : params.manufacturer.split(",").map((m) => m.trim()); | ||
| where.manufacturer = In(manufacturers); | ||
| } |
Comment on lines
+60
to
+67
| // 4. Price Filter | ||
| if (params.minPrice !== undefined && params.maxPrice !== undefined) { | ||
| where.price = Between(Number(params.minPrice), Number(params.maxPrice)); | ||
| } else if (params.minPrice !== undefined) { | ||
| where.price = MoreThanOrEqual(Number(params.minPrice)); | ||
| } else if (params.maxPrice !== undefined) { | ||
| where.price = LessThanOrEqual(Number(params.maxPrice)); | ||
| } |
Comment on lines
+18
to
+27
| const queryParams: ProductQueryParams = { | ||
| page: page ? Number(page) : undefined, | ||
| limit: limit ? Number(limit) : undefined, | ||
| search: typeof search === 'string' ? search : undefined, | ||
| category: typeof category === 'string' ? category : undefined, | ||
| manufacturer: typeof manufacturer === 'string' ? manufacturer : undefined, | ||
| minPrice: minPrice ? Number(minPrice) : undefined, | ||
| maxPrice: maxPrice ? Number(maxPrice) : undefined, | ||
| sortBy: sortBy as ProductQueryParams['sortBy'], | ||
| }; |
Comment on lines
+14
to
+17
| const category = await this.categoryRepo.findOne({ | ||
| where: { slug }, | ||
| relations: { products: true }, | ||
| }); |
Comment on lines
+16
to
+26
| const userId = req.headers['x-user-id'] as string; | ||
| const userEmail = req.headers['x-user-email'] as string; | ||
| const userName = req.headers['x-user-name'] as string; | ||
|
|
||
| if (userId) { | ||
| req.user = { | ||
| id: userId, | ||
| email: userEmail, | ||
| name: userName, | ||
| }; | ||
| } |
Comment on lines
+29
to
+32
| app.use("/api/v1/core", routes); | ||
| app.use("/", routes); | ||
|
|
||
| app.use(errorHandler); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary of Changes
routes/->controllers/->services/->entities/.findAndCount,findOne,find,ILike,In,Between,MoreThanOrEqual,LessThanOrEqual).GET /categories&GET /categories/:slug(with product counts and category metadata).GET /products(multi-faceted search, price bounds, category/manufacturer filtering, pagination).GET /products/:identifier(single product spec sheet).GET/POST/PUT/DELETE /projects(saved builds and chat history CRUD).