Skip to content

post route and service - #16

Merged
OumB2021 merged 3 commits into
mainfrom
post_service
Jul 4, 2026
Merged

post route and service#16
OumB2021 merged 3 commits into
mainfrom
post_service

Conversation

@OumB2021

@OumB2021 OumB2021 commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Added full post management in the API: feed/listing, viewing, authenticated create/update/delete, and user-scoped retrieval.
    • Added a user-aware rate limit for post creation and stricter, validated feed/query parameters.
    • Post responses now include author details.
  • Bug Fixes
    • Improved error handling with more accurate HTTP statuses (unauthorized/not found/forbidden) and consistent error payloads.
    • Updated validation to prevent empty PATCH payloads and ensured soft-deleted posts are excluded from results.

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 98eefaf1-8661-4fed-b2c8-1d911b616cdf

📥 Commits

Reviewing files that changed from the base of the PR and between 5bfcc70 and 73ce385.

📒 Files selected for processing (1)
  • packages/shared/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/shared/src/index.ts

📝 Walkthrough

Walkthrough

Adds shared post validation schemas, status-aware API error handling, user-scoped post rate limiting, post read/write service methods, and authenticated post routes. It also switches the shared package to ESM and replaces db wildcard exports with explicit named exports.

Changes

Post API and shared schema updates

Layer / File(s) Summary
Shared post schemas and module exports
packages/shared/package.json, packages/shared/src/index.ts, packages/db/src/index.ts
Adds feed/query/param schemas, tightens update validation to reject empty payloads, marks the shared package as ESM, and replaces db wildcard exports with explicit tables, relations, and types.
HttpError response handling
apps/api/src/lib/httpError.ts, apps/api/src/middleware/errorHandler.ts
Adds HttpError and teaches the error middleware to return its status and message as JSON while preserving existing validation and fallback handling.
Authenticated post creation rate limit
apps/api/src/middleware/rateLimit.ts
Adds a Clerk-aware create-post limiter that keys on user id when available and otherwise falls back to IP-based limiting.
Post service reads and mutations
apps/api/src/services/postService.ts
Implements feed, single-post, and user-post reads plus create, update, and soft-delete mutations with author data and ownership checks.
Post router wiring
apps/api/src/routes/posts.ts
Adds feed/user/id post routes, authenticated create/update/delete endpoints, Zod validation, and service delegation with error forwarding.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant postsRouter
  participant Clerk
  participant postService
  participant Database

  Client->>postsRouter: GET /user/:userId
  postsRouter->>postService: getUserPosts(userId)
  postService->>Database: select non-deleted posts
  Database-->>postService: rows
  postService-->>postsRouter: data
  postsRouter-->>Client: { success, data }

  Client->>postsRouter: POST /
  postsRouter->>Clerk: getAuth(req)
  Clerk-->>postsRouter: userId
  postsRouter->>postService: createPost(authorId, input)
  postService->>Database: insert post
  Database-->>postService: inserted row
  postService-->>postsRouter: data
  postsRouter-->>Client: 201 { success, data }
Loading

Possibly related PRs

  • OumB2021/Journal#12: Modifies the same shared post schemas, including updatePostSchema, that the new post routes use for request validation.
  • OumB2021/Journal#13: Touches the same errorHandler middleware path and error response logic.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and matches the main changes to the post route and service implementation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch post_service

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/routes/posts.ts`:
- Around line 11-14: The posts route is validating query params locally and
leaving path params unvalidated, so it bypasses the shared Zod contract and
unknown-field rejection. Update the handlers in posts.ts to use schemas from
`@journal/shared` (or add the missing feed/query/param schemas there), apply
.strict() where appropriate, and parse req.query plus req.params.id /
req.params.userId before calling postService.
- Around line 21-79: The posts routes currently have no visible rate limiting,
and the `postsRouter.post("/")` create-post endpoint in particular needs a
tighter per-user Redis-backed limit. Add or wire in rate-limiting middleware on
the relevant handlers in `postsRouter`, using per-user limits for authenticated
routes and per-IP limits for unauthenticated ones, and make sure `createPost`
gets the stricter policy. If this is intentionally enforced elsewhere, confirm
that `postsRouter` and `postService.createPost` are covered by that shared
middleware.
- Around line 16-19: The post routes are using a local auth helper instead of
the shared middleware, which makes protection easy to bypass on future handlers.
Replace the per-handler `requireAuthed` check in `posts.ts` with the existing
`requireAuth` middleware from `middleware/requireAuth`, and attach it in the
router chain for the protected post mutations so `create`, `update`, and similar
handlers are guarded consistently.

In `@apps/api/src/services/postService.ts`:
- Around line 58-67: The `updatePost` flow can reach `db.update(posts).set()`
with an empty object because `updatePostSchema` is partial, which leads to
invalid SQL. Add a no-op guard in `postService.updatePost` before building the
`.set()` payload, or tighten `updatePostSchema`, so an empty PATCH body returns
a clear 400 instead of calling the database. Use the `updatePost` method and the
`updatePostSchema` validation path as the main places to fix this.
- Around line 52-70: The updatePost flow currently checks ownership from
getPostById() but the UPDATE itself does not enforce the soft-delete boundary,
so a row deleted after the read can still be mutated. Update the updatePost
query to include the same soft-delete guard in its where clause by combining
eq(posts.id, id) with isNull(posts.deletedAt), keeping the read/write behavior
consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1eccf4e7-c70d-4e75-b754-06b7e4925193

📥 Commits

Reviewing files that changed from the base of the PR and between 5f40261 and de5e9e6.

📒 Files selected for processing (5)
  • apps/api/src/lib/httpError.ts
  • apps/api/src/middleware/errorHandler.ts
  • apps/api/src/routes/posts.ts
  • apps/api/src/services/postService.ts
  • packages/db/src/index.ts

Comment thread apps/api/src/routes/posts.ts Outdated
Comment on lines +16 to +19
function requireAuthed(req: Request) {
const { isAuthenticated } = getAuth(req);
if (!isAuthenticated) throw new HttpError(401, "Unauthorized");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect requireAuth.ts middleware exports
ast-grep outline apps/api/src/middleware/requireAuth.ts --view expanded
rg -n 'export (function|const)' apps/api/src/middleware/requireAuth.ts

Repository: OumB2021/Journal

Length of output: 464


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,180p' apps/api/src/routes/posts.ts
printf '\n---\n'
ast-grep outline apps/api/src/middleware/requireAuth.ts --view expanded
printf '\n---\n'
rg -n 'requireAuth\\(|requireAuthed\\(' apps/api/src/routes apps/api/src/middleware

Repository: OumB2021/Journal

Length of output: 2677


Use the shared requireAuth middleware here
apps/api/src/middleware/requireAuth.ts already exports requireAuth(req, res, next). Attach it to the protected post routes instead of calling requireAuthed() inside each handler so auth is enforced by the router chain and can’t be skipped on new mutations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/routes/posts.ts` around lines 16 - 19, The post routes are using
a local auth helper instead of the shared middleware, which makes protection
easy to bypass on future handlers. Replace the per-handler `requireAuthed` check
in `posts.ts` with the existing `requireAuth` middleware from
`middleware/requireAuth`, and attach it in the router chain for the protected
post mutations so `create`, `update`, and similar handlers are guarded
consistently.

Source: Coding guidelines

Comment thread apps/api/src/routes/posts.ts
Comment thread apps/api/src/services/postService.ts
Comment on lines +58 to +67
const [updated] = await db
.update(posts)
.set({
...(input.title !== undefined && { title: input.title }),
...(input.body !== undefined && { body: input.body }),
...(input.image_url !== undefined && { imageUrl: input.image_url }),
...(input.category !== undefined && { category: input.category }),
})
.where(eq(posts.id, id))
.returning();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does drizzle-orm's PostgreSQL update().set({}) with no keys throw an error or generate invalid SQL?

💡 Result:

In Drizzle ORM, executing a PostgreSQL update().set({}) with an empty object will generate invalid SQL and will not throw an error at the Drizzle level [1]. When you provide an empty object to the set() method, Drizzle generates an SQL statement resembling UPDATE "table_name" SET WHERE ... [1]. Because the SET clause is empty, this is invalid PostgreSQL syntax, and the database driver will return a syntax error [1]. This behavior is well-documented in community issue reports as a known issue [1]. Drizzle does not currently perform a validation check to prevent the generation of such invalid SQL queries when the update set is empty [1]. Developers are advised to handle this case in their own application logic (e.g., by checking if the object contains keys before initiating the update) to avoid generating invalid queries [1].

Citations:


🏁 Script executed:

git ls-files 'apps/api/src/services/postService.ts' 'package.json' 'apps/api/package.json' '**/package.json' | sed -n '1,200p'

Repository: OumB2021/Journal

Length of output: 334


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== apps/api/package.json ==\n'
cat -n apps/api/package.json

printf '\n== packages/db/package.json ==\n'
cat -n packages/db/package.json

printf '\n== apps/api/src/services/postService.ts (1-220) ==\n'
sed -n '1,220p' apps/api/src/services/postService.ts | cat -n

printf '\n== search for update schema / post update route ==\n'
rg -n "update.*post|updatePost|patch.*post|No fields provided|empty.*body|zod.*post|partial\\(" apps/api/src packages/shared/src packages/db/src -S

Repository: OumB2021/Journal

Length of output: 6003


Handle empty post updates before calling .set()

updatePostSchema allows {} through partial(), so an empty PATCH body reaches this path and db.update(...).set({}) will emit invalid SET SQL. Reject no-op updates here or in the schema to return a clear 400 instead of a driver error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/services/postService.ts` around lines 58 - 67, The `updatePost`
flow can reach `db.update(posts).set()` with an empty object because
`updatePostSchema` is partial, which leads to invalid SQL. Add a no-op guard in
`postService.updatePost` before building the `.set()` payload, or tighten
`updatePostSchema`, so an empty PATCH body returns a clear 400 instead of
calling the database. Use the `updatePost` method and the `updatePostSchema`
validation path as the main places to fix this.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (1)
apps/api/src/middleware/rateLimit.ts (1)

39-56: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate rate-limit boilerplate.

createPostRateLimit repeats the exact consume/try-catch/429 pattern from rateLimit (Lines 13-28). Extracting a small factory would prevent the two implementations from drifting as more per-route limiters are added.

♻️ Proposed refactor to share the consume/error-handling logic
+function makeRateLimitMiddleware(
+  limiter: RateLimiterRedis,
+  keyFn: (req: Request) => string,
+) {
+  return async function (req: Request, res: Response, next: NextFunction) {
+    try {
+      await limiter.consume(keyFn(req));
+      next();
+    } catch (err) {
+      if (!(err instanceof Error)) {
+        res.status(429).json({ error: "Too many requests" });
+      } else {
+        next(err);
+      }
+    }
+  };
+}
+
-export async function createPostRateLimit(
-  req: Request,
-  res: Response,
-  next: NextFunction,
-) {
-  const { userId } = getAuth(req);
-
-  try {
-    await createPostLimiter.consume(userId ?? req.ip ?? "unknown");
-    next();
-  } catch (err) {
-    if (!(err instanceof Error)) {
-      res.status(429).json({ error: "Too many requests" });
-    } else {
-      next(err);
-    }
-  }
-}
+export const createPostRateLimit = makeRateLimitMiddleware(
+  createPostLimiter,
+  (req) => getAuth(req).userId ?? req.ip ?? "unknown",
+);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/middleware/rateLimit.ts` around lines 39 - 56,
createPostRateLimit duplicates the same consume/try-catch/429 handling already
implemented in rateLimit, so extract that shared logic into a small reusable
factory/helper and have both middleware functions use it. Keep the
route-specific limiter injection (for createPostLimiter and the existing
limiter) but centralize the request key selection, consume call, and error
handling so future per-route limiters don’t drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/shared/src/index.ts`:
- Around line 13-17: The updatePostSchema in index.ts currently uses
.partial().refine(), which wraps the schema and allows unknown fields to slip
through in PATCH bodies. Update createPostSchema’s derived schema so it applies
.strict() before the refine check, ensuring extra keys are rejected while still
requiring at least one provided field. Keep the fix anchored around
updatePostSchema and its parse path used by the PATCH route.

---

Nitpick comments:
In `@apps/api/src/middleware/rateLimit.ts`:
- Around line 39-56: createPostRateLimit duplicates the same
consume/try-catch/429 handling already implemented in rateLimit, so extract that
shared logic into a small reusable factory/helper and have both middleware
functions use it. Keep the route-specific limiter injection (for
createPostLimiter and the existing limiter) but centralize the request key
selection, consume call, and error handling so future per-route limiters don’t
drift.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 78eaacea-3f58-4131-8b97-b318632d75c1

📥 Commits

Reviewing files that changed from the base of the PR and between de5e9e6 and 5bfcc70.

📒 Files selected for processing (5)
  • apps/api/src/middleware/rateLimit.ts
  • apps/api/src/routes/posts.ts
  • apps/api/src/services/postService.ts
  • packages/shared/package.json
  • packages/shared/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/api/src/routes/posts.ts
  • apps/api/src/services/postService.ts

Comment thread packages/shared/src/index.ts
@OumB2021
OumB2021 merged commit 378ca36 into main Jul 4, 2026
6 checks passed
This was referenced Jul 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant