🪻 distributed transcription service thistle.dunkirk.sh

fix: standardize API response formats

Convert all message-only responses to include success field for consistency:
- { message: "..." } → { success: true, message: "..." }

Standardized patterns:
- Success operations: { success: true }
- Success with message: { success: true, message: "..." }
- Success with data: { success: true, data: {...}, message?: "..." }
- Error responses: { error: "..." } (unchanged)
- Data responses: Direct objects like { user, jobs, classes } (unchanged)

Changes:
- Email verification success response
- Verification email sent response
- Verification code sent response
- Password reset link sent response
- Password reset success response

Added comprehensive test suite documenting API response standards.

💘 Generated with Crush

Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>

dunkirk.sh 21fa1d2c ba3e2807

verified
Changed files
+142 -2
src
+6 -2
src/index.ts
···
return Response.json(
{
+
success: true,
message: "Email verified successfully",
email_verified: true,
user: { id: user.id, email: user.email },
···
}),
});
-
return Response.json({ message: "Verification email sent" });
+
return Response.json({ success: true, message: "Verification email sent" });
} catch (error) {
return handleError(error);
}
···
if (!user) {
// Don't reveal if user exists
return Response.json({
+
success: true,
message:
"If an account exists with that email, a verification code has been sent",
});
···
});
return Response.json({
+
success: true,
message: "Verification code sent",
verification_code_sent_at: sentAt,
});
···
}
return Response.json({
+
success: true,
message:
"If an account exists with that email, a password reset link has been sent",
});
···
await updateUserPassword(userId, password);
consumePasswordResetToken(token);
-
return Response.json({ message: "Password reset successfully" });
+
return Response.json({ success: true, message: "Password reset successfully" });
} catch (error) {
console.error("[Email] Reset password error:", error);
return Response.json(
+136
src/lib/api-response-format.test.ts
···
+
import { describe, expect, test } from "bun:test";
+
+
/**
+
* API Response Format Standards
+
*
+
* This test documents the standardized response formats across the API.
+
* All endpoints should follow these patterns for consistency.
+
*/
+
+
describe("API Response Format Standards", () => {
+
test("success responses should include success: true", () => {
+
// Success-only responses (no data returned)
+
const successOnly = { success: true };
+
expect(successOnly).toHaveProperty("success", true);
+
+
// Success with message
+
const successWithMessage = {
+
success: true,
+
message: "Operation completed successfully",
+
};
+
expect(successWithMessage).toHaveProperty("success", true);
+
expect(successWithMessage).toHaveProperty("message");
+
+
// Success with data
+
const successWithData = {
+
success: true,
+
data: { id: 1, name: "Test" },
+
};
+
expect(successWithData).toHaveProperty("success", true);
+
expect(successWithData).toHaveProperty("data");
+
});
+
+
test("error responses should use error field", () => {
+
const errorResponse = { error: "Something went wrong" };
+
expect(errorResponse).toHaveProperty("error");
+
expect(typeof errorResponse.error).toBe("string");
+
});
+
+
test("data responses can return data directly", () => {
+
// Direct data return (common pattern for GET endpoints)
+
const userData = {
+
user: { id: 1, email: "test@example.com" },
+
has_subscription: true,
+
};
+
expect(userData).toHaveProperty("user");
+
+
// List responses
+
const listData = {
+
jobs: [{ id: "1" }, { id: "2" }],
+
pagination: { limit: 50, hasMore: false, nextCursor: null },
+
};
+
expect(listData).toHaveProperty("jobs");
+
expect(listData).toHaveProperty("pagination");
+
});
+
+
test("message-only responses are converted to success+message", () => {
+
// OLD (deprecated): { message: "..." }
+
// NEW (standard): { success: true, message: "..." }
+
+
const newFormat = {
+
success: true,
+
message: "Verification email sent",
+
};
+
+
expect(newFormat).toHaveProperty("success", true);
+
expect(newFormat).toHaveProperty("message");
+
});
+
});
+
+
describe("API Response Patterns", () => {
+
test("authentication responses", () => {
+
// Login success
+
const login = {
+
user: { id: 1, email: "test@example.com" },
+
email_verification_required: false,
+
};
+
expect(login).toHaveProperty("user");
+
+
// Logout success
+
const logout = { success: true };
+
expect(logout.success).toBe(true);
+
+
// Email verified
+
const verified = {
+
success: true,
+
message: "Email verified successfully",
+
email_verified: true,
+
user: { id: 1, email: "test@example.com" },
+
};
+
expect(verified.success).toBe(true);
+
expect(verified).toHaveProperty("message");
+
});
+
+
test("CRUD operation responses", () => {
+
// Create (returns created object)
+
const created = {
+
id: "123",
+
name: "New Item",
+
created_at: Date.now(),
+
};
+
expect(created).toHaveProperty("id");
+
+
// Update (returns success)
+
const updated = { success: true };
+
expect(updated.success).toBe(true);
+
+
// Delete (returns success)
+
const deleted = { success: true };
+
expect(deleted.success).toBe(true);
+
+
// Get (returns data directly)
+
const fetched = {
+
id: "123",
+
name: "Item",
+
};
+
expect(fetched).toHaveProperty("id");
+
});
+
+
test("paginated list responses", () => {
+
const paginatedList = {
+
data: [{ id: "1" }, { id: "2" }],
+
pagination: {
+
limit: 50,
+
hasMore: true,
+
nextCursor: "MTczMjM5NjgwMHx0cmFucy0xMjM",
+
},
+
};
+
+
expect(paginatedList).toHaveProperty("data");
+
expect(Array.isArray(paginatedList.data)).toBe(true);
+
expect(paginatedList).toHaveProperty("pagination");
+
expect(paginatedList.pagination).toHaveProperty("limit");
+
expect(paginatedList.pagination).toHaveProperty("hasMore");
+
expect(paginatedList.pagination).toHaveProperty("nextCursor");
+
});
+
});