Refactor API to use nested sessions/devices routers and fix test infrastructure
- Update API contract to use nested router structure for sessions and devices (me.sessions.list, me.devices.getInfo, etc.) - Update frontend Svelte components to use new nested API paths - Fix test assertion patterns for consistency (remove async () => wrappers) - Fix test-db.ts findRepoRoot to use existsSync for directory checking (Bun.file().exists() returns false for directories) - Add ESLint config override for test files to handle expect().rejects patterns Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,11 @@ import {
|
||||
signupInputSchema,
|
||||
verifyEmailInputSchema,
|
||||
} from "./schemas/auth.js";
|
||||
import { emailSchema, slugSchema } from "./schemas/common.js";
|
||||
import {
|
||||
emailSchema,
|
||||
slugSchema,
|
||||
successResponseSchema,
|
||||
} from "./schemas/common.js";
|
||||
import {
|
||||
createInviteInputSchema,
|
||||
createOrgInputSchema,
|
||||
@@ -51,26 +55,32 @@ import {
|
||||
export const contract = oc.router({
|
||||
auth: oc.router({
|
||||
// Signup and verification
|
||||
signup: oc.input(signupInputSchema).output(z.void()),
|
||||
verifyEmail: oc.input(verifyEmailInputSchema).output(z.void()),
|
||||
resendVerificationEmail: oc.output(z.void()),
|
||||
signup: oc.input(signupInputSchema).output(successResponseSchema),
|
||||
verifyEmail: oc.input(verifyEmailInputSchema).output(successResponseSchema),
|
||||
resendVerificationEmail: oc.output(successResponseSchema),
|
||||
|
||||
// Login flow
|
||||
createLoginRequest: oc
|
||||
.input(loginRequestInputSchema)
|
||||
.output(loginRequestOutputSchema),
|
||||
loginPassword: oc.input(loginPasswordInputSchema).output(z.void()),
|
||||
loginPassword: oc
|
||||
.input(loginPasswordInputSchema)
|
||||
.output(successResponseSchema),
|
||||
loginPasswordConfirm: oc
|
||||
.input(z.object({ token: z.string() }))
|
||||
.output(z.void()),
|
||||
.output(successResponseSchema),
|
||||
loginIfRequestIsCompleted: oc.output(loginStatusOutputSchema),
|
||||
|
||||
// Password reset
|
||||
forgotPassword: oc.input(forgotPasswordInputSchema).output(z.void()),
|
||||
resetPassword: oc.input(resetPasswordInputSchema).output(z.void()),
|
||||
forgotPassword: oc
|
||||
.input(forgotPasswordInputSchema)
|
||||
.output(successResponseSchema),
|
||||
resetPassword: oc
|
||||
.input(resetPasswordInputSchema)
|
||||
.output(successResponseSchema),
|
||||
|
||||
// Logout
|
||||
logout: oc.output(z.void()),
|
||||
logout: oc.output(successResponseSchema),
|
||||
|
||||
// WebAuthn procedures
|
||||
webauthn: oc.router({
|
||||
@@ -103,45 +113,59 @@ export const contract = oc.router({
|
||||
response: z.custom<AuthenticationResponseJSON>(),
|
||||
}),
|
||||
)
|
||||
.output(z.void()),
|
||||
.output(successResponseSchema),
|
||||
}),
|
||||
}),
|
||||
|
||||
me: oc.router({
|
||||
// Profile
|
||||
get: oc.output(userProfileSchema),
|
||||
setupProfile: oc.input(setupProfileInputSchema).output(z.void()),
|
||||
updateProfile: oc.input(updateProfileInputSchema).output(z.void()),
|
||||
delete: oc.input(z.object({ password: z.string() })).output(z.void()),
|
||||
setupProfile: oc
|
||||
.input(setupProfileInputSchema)
|
||||
.output(successResponseSchema),
|
||||
updateProfile: oc
|
||||
.input(updateProfileInputSchema)
|
||||
.output(successResponseSchema),
|
||||
delete: oc
|
||||
.input(z.object({ password: z.string() }))
|
||||
.output(successResponseSchema),
|
||||
|
||||
// Auth status (for CLI and debugging)
|
||||
authStatus: oc.output(authStatusOutputSchema),
|
||||
|
||||
// Authentication settings
|
||||
setPassword: oc.input(setPasswordInputSchema).output(z.void()),
|
||||
setPassword: oc.input(setPasswordInputSchema).output(successResponseSchema),
|
||||
|
||||
// Passkeys
|
||||
passkeys: oc.router({
|
||||
list: oc.output(z.array(passkeyOutputSchema)),
|
||||
rename: oc
|
||||
.input(z.object({ passkeyId: z.number(), name: z.string() }))
|
||||
.output(z.void()),
|
||||
delete: oc.input(z.object({ passkeyId: z.number() })).output(z.void()),
|
||||
.output(successResponseSchema),
|
||||
delete: oc
|
||||
.input(z.object({ passkeyId: z.number() }))
|
||||
.output(successResponseSchema),
|
||||
}),
|
||||
|
||||
// Sessions & devices
|
||||
listSessions: oc.output(z.array(sessionOutputSchema)),
|
||||
revokeSession: oc
|
||||
.input(z.object({ sessionId: z.number() }))
|
||||
.output(z.void()),
|
||||
revokeAllSessions: oc.output(z.void()),
|
||||
getDeviceInfo: oc.output(deviceOutputSchema),
|
||||
trustDevice: oc.input(trustDeviceInputSchema).output(z.void()),
|
||||
listTrustedDevices: oc.output(z.array(deviceOutputSchema)),
|
||||
untrustDevice: oc
|
||||
.input(z.object({ deviceId: z.number() }))
|
||||
.output(z.void()),
|
||||
revokeAllTrustedDevices: oc.output(z.void()),
|
||||
// Sessions
|
||||
sessions: oc.router({
|
||||
list: oc.output(z.array(sessionOutputSchema)),
|
||||
revoke: oc
|
||||
.input(z.object({ sessionId: z.number() }))
|
||||
.output(successResponseSchema),
|
||||
revokeAll: oc.output(successResponseSchema),
|
||||
}),
|
||||
|
||||
// Devices
|
||||
devices: oc.router({
|
||||
getInfo: oc.output(deviceOutputSchema),
|
||||
trust: oc.input(trustDeviceInputSchema).output(successResponseSchema),
|
||||
listTrusted: oc.output(z.array(deviceOutputSchema)),
|
||||
untrust: oc
|
||||
.input(z.object({ deviceId: z.number() }))
|
||||
.output(successResponseSchema),
|
||||
revokeAll: oc.output(successResponseSchema),
|
||||
}),
|
||||
}),
|
||||
|
||||
orgs: oc.router({
|
||||
@@ -159,19 +183,25 @@ export const contract = oc.router({
|
||||
logoUrl: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.output(z.void()),
|
||||
delete: oc.input(z.object({ slug: slugSchema })).output(z.void()),
|
||||
leave: oc.input(z.object({ slug: slugSchema })).output(z.void()),
|
||||
.output(successResponseSchema),
|
||||
delete: oc
|
||||
.input(z.object({ slug: slugSchema }))
|
||||
.output(successResponseSchema),
|
||||
leave: oc
|
||||
.input(z.object({ slug: slugSchema }))
|
||||
.output(successResponseSchema),
|
||||
|
||||
// Members
|
||||
members: oc.router({
|
||||
list: oc
|
||||
.input(z.object({ slug: slugSchema }))
|
||||
.output(z.array(orgMemberOutputSchema)),
|
||||
updateRole: oc.input(updateMemberRoleInputSchema).output(z.void()),
|
||||
updateRole: oc
|
||||
.input(updateMemberRoleInputSchema)
|
||||
.output(successResponseSchema),
|
||||
remove: oc
|
||||
.input(z.object({ slug: slugSchema, userId: z.number() }))
|
||||
.output(z.void()),
|
||||
.output(successResponseSchema),
|
||||
}),
|
||||
|
||||
// Invites
|
||||
@@ -179,11 +209,13 @@ export const contract = oc.router({
|
||||
list: oc
|
||||
.input(z.object({ slug: slugSchema }))
|
||||
.output(z.array(orgInviteOutputSchema)),
|
||||
create: oc.input(createInviteInputSchema).output(z.void()),
|
||||
create: oc.input(createInviteInputSchema).output(successResponseSchema),
|
||||
cancel: oc
|
||||
.input(z.object({ slug: slugSchema, inviteId: z.number() }))
|
||||
.output(z.void()),
|
||||
accept: oc.input(z.object({ token: z.string() })).output(z.void()),
|
||||
.output(successResponseSchema),
|
||||
accept: oc
|
||||
.input(z.object({ token: z.string() }))
|
||||
.output(successResponseSchema),
|
||||
}),
|
||||
|
||||
// Sites
|
||||
@@ -210,31 +242,39 @@ export const contract = oc.router({
|
||||
logoUrl: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.output(z.void()),
|
||||
delete: oc.input(z.object({ slug: slugSchema })).output(z.void()),
|
||||
.output(successResponseSchema),
|
||||
delete: oc
|
||||
.input(z.object({ slug: slugSchema }))
|
||||
.output(successResponseSchema),
|
||||
listSites: oc
|
||||
.input(z.object({ slug: slugSchema }))
|
||||
.output(z.array(orgSiteOutputSchema)),
|
||||
addSite: oc.input(adminAddSiteInputSchema).output(z.void()),
|
||||
addSite: oc.input(adminAddSiteInputSchema).output(successResponseSchema),
|
||||
removeSite: oc
|
||||
.input(z.object({ slug: slugSchema, domain: z.string() }))
|
||||
.output(z.void()),
|
||||
.output(successResponseSchema),
|
||||
}),
|
||||
|
||||
// Admin user management
|
||||
users: oc.router({
|
||||
list: oc.output(z.array(userProfileSchema)),
|
||||
get: oc.input(z.object({ email: emailSchema })).output(userProfileSchema),
|
||||
create: oc.input(adminCreateUserInputSchema).output(z.void()),
|
||||
update: oc.input(adminUpdateUserInputSchema).output(z.void()),
|
||||
confirmEmail: oc.input(z.object({ email: emailSchema })).output(z.void()),
|
||||
create: oc
|
||||
.input(adminCreateUserInputSchema)
|
||||
.output(successResponseSchema),
|
||||
update: oc
|
||||
.input(adminUpdateUserInputSchema)
|
||||
.output(successResponseSchema),
|
||||
confirmEmail: oc
|
||||
.input(z.object({ email: emailSchema }))
|
||||
.output(successResponseSchema),
|
||||
}),
|
||||
|
||||
// Admin auth management
|
||||
auth: oc.router({
|
||||
completeLogin: oc
|
||||
.input(z.object({ email: emailSchema }))
|
||||
.output(z.void()),
|
||||
.output(successResponseSchema),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -58,3 +58,9 @@ export const phoneSchema = z
|
||||
.refine((val) => !val || isValidPhoneNumber(val), {
|
||||
message: "Invalid phone number",
|
||||
});
|
||||
|
||||
/**
|
||||
* Success response schema for operations that don't return data
|
||||
* Use instead of void to make responses more explicit
|
||||
*/
|
||||
export const successResponseSchema = z.object({ success: z.literal(true) });
|
||||
|
||||
Reference in New Issue
Block a user