Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions packages/api/src/middleware/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,11 @@ import type { MiddlewareHandler } from "hono"
* GET, POST → public (no token required)
* PUT, DELETE, PATCH → require Bearer token
*
* BUG: The allow-list check uses `'post'` (lowercase) instead of `'POST'`.
* HTTP methods are always uppercase per RFC 7231, so POST is never matched
* as a public method — POST requests incorrectly require a token.
*
* Fix: change `'post'` to `'POST'` in the public methods array.
* Method names are compared against `c.req.method`, which is always uppercase
* per RFC 7231, so the allow-list entries must be uppercase too.
*/
export const authMiddleware: MiddlewareHandler = async (c, next) => {
// BUG: 'post' should be 'POST' — POST is never treated as public
const publicMethods = ["GET", "post"]
const publicMethods = ["GET", "POST"]

if (publicMethods.includes(c.req.method)) {
return next()
Expand Down
6 changes: 1 addition & 5 deletions packages/api/src/routes/users.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { Hono } from "hono"
import { db } from "../lib/db"
import { notFound } from "../lib/errors"
// BUG: missing import — `badRequest` is used below but not imported here.
// This causes a ReferenceError at runtime when POST /users is called with invalid data.
// Fix: add `badRequest` to the import from "../lib/errors"
import { notFound, badRequest } from "../lib/errors"

const router = new Hono()

Expand All @@ -20,7 +17,6 @@ router.get("/:id", (c) => {
router.post("/", async (c) => {
const body = await c.req.json().catch(() => null)
if (!body || !body.username || !body.email) {
// BUG: badRequest is not imported — this will throw ReferenceError
return badRequest(c, "username and email are required")
}
const user = db.users.create({ username: body.username, email: body.email })
Expand Down
6 changes: 1 addition & 5 deletions packages/shared/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
/**
* Shared types used by both the API and any consumers.
*
* BUG: The field is named `userName` here but the API routes reference `username`
* (lowercase n). This causes a type error in routes/users.ts and a runtime
* mismatch when serialising responses.
*/

export type User = {
id: string
userName: string // BUG: should be `username` to match API usage
username: string
email: string
createdAt: string
}
Expand Down
35 changes: 32 additions & 3 deletions packages/shared/src/utils/pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,38 @@ import type { PaginatedResponse } from "../types"
* @param page 1-indexed page number
* @param size Number of items per page
*
* TODO: implement this function — it is currently a stub.
* The test in packages/shared/test/pagination.test.ts exercises the full contract.
* Inputs are clamped rather than throwing, so every field of the returned
* `PaginatedResponse` is always a finite, JSON-serialisable number:
*
* - `page` is truncated to an integer and clamped to a minimum of 1. Values
* below 1 are treated as page 1, so the slice start can never go negative — a
* negative start would otherwise be reinterpreted by `Array.slice` as an
* offset from the end and return a bogus mid-array window.
* - A non-finite `page` (`NaN` / `±Infinity`) is *not* treated as page 1, which
* would return real first-page data indistinguishable from a genuine
* first-page request. It is instead normalised to `totalPages + 1`, the first
* page past the end of `items`, so the request is reported as out-of-range and
* `data` comes back empty — consistent with a finite too-large `page`.
* - `size` is truncated to an integer and clamped to a minimum of 1. This keeps
* `totalPages` a valid non-negative integer; an unclamped `size <= 0`, `NaN`,
* or fractional size would produce `Infinity` / `NaN` / a negative count,
* which serialises to JSON `null` and violates the declared `number` type.
*
* The clamped values are what get reported back in `page` and `pageSize`, so
* the response always describes the window actually returned.
*
* Pages past the end of `items` yield an empty `data` array rather than throwing.
*/
export function paginate<T>(items: T[], page: number, size: number): PaginatedResponse<T> {
throw new Error("not implemented")
const safeSize = Number.isFinite(size) ? Math.max(1, Math.trunc(size)) : 1
const totalPages = Math.ceil(items.length / safeSize)
const safePage = Number.isFinite(page) ? Math.max(1, Math.trunc(page)) : totalPages + 1
const start = (safePage - 1) * safeSize
return {
data: items.slice(start, start + safeSize),
page: safePage,
pageSize: safeSize,
total: items.length,
totalPages,
}
}
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"moduleResolution": "bundler",
"strict": true,
"skipLibCheck": true,
"types": ["bun-types"],
"paths": {
"@e2e/shared": ["./packages/shared/src/index.ts"]
}
Expand Down