Add oRPC API contract with Zod schemas
- Create @reviq/api-contract package - Define Zod schemas for all input/output types: - Auth schemas (signup, login, password reset, WebAuthn) - User/profile schemas - Organization schemas (CRUD, members, invites) - Admin procedure schemas - Define oRPC contract with full procedure signatures: - auth.* (signup, login, password reset, WebAuthn) - me.* (profile, sessions, devices, passkeys) - orgs.* (CRUD, members, invites, sites) - admin.* (orgs, users, auth management) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
228
packages/api-contract/src/contract.ts
Normal file
228
packages/api-contract/src/contract.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
import { oc } from "@orpc/contract";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
signupInputSchema,
|
||||
verifyEmailInputSchema,
|
||||
loginRequestInputSchema,
|
||||
loginRequestOutputSchema,
|
||||
loginPasswordInputSchema,
|
||||
loginStatusOutputSchema,
|
||||
forgotPasswordInputSchema,
|
||||
resetPasswordInputSchema,
|
||||
} from "./schemas/auth.js";
|
||||
import {
|
||||
userProfileSchema,
|
||||
setupProfileInputSchema,
|
||||
updateProfileInputSchema,
|
||||
setPasswordInputSchema,
|
||||
passkeyOutputSchema,
|
||||
sessionOutputSchema,
|
||||
deviceOutputSchema,
|
||||
trustDeviceInputSchema,
|
||||
} from "./schemas/user.js";
|
||||
import {
|
||||
createOrgInputSchema,
|
||||
orgOutputSchema,
|
||||
orgMemberOutputSchema,
|
||||
updateMemberRoleInputSchema,
|
||||
createInviteInputSchema,
|
||||
orgInviteOutputSchema,
|
||||
orgSiteOutputSchema,
|
||||
} from "./schemas/org.js";
|
||||
import {
|
||||
adminCreateOrgInputSchema,
|
||||
adminCreateUserInputSchema,
|
||||
adminUpdateUserInputSchema,
|
||||
adminAddSiteInputSchema,
|
||||
} from "./schemas/admin.js";
|
||||
import { emailSchema, slugSchema } from "./schemas/common.js";
|
||||
|
||||
/**
|
||||
* oRPC API Contract for the Publisher Dashboard
|
||||
* This defines all RPC procedure signatures served at /api/v1/rpc
|
||||
*/
|
||||
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()),
|
||||
|
||||
// Login flow
|
||||
createLoginRequest: oc
|
||||
.input(loginRequestInputSchema)
|
||||
.output(loginRequestOutputSchema),
|
||||
loginPassword: oc.input(loginPasswordInputSchema).output(z.void()),
|
||||
loginPasswordConfirm: oc
|
||||
.input(z.object({ token: z.string() }))
|
||||
.output(z.void()),
|
||||
loginIfRequestIsCompleted: oc.output(loginStatusOutputSchema),
|
||||
|
||||
// Password reset
|
||||
forgotPassword: oc.input(forgotPasswordInputSchema).output(z.void()),
|
||||
resetPassword: oc.input(resetPasswordInputSchema).output(z.void()),
|
||||
|
||||
// Logout
|
||||
logout: oc.output(z.void()),
|
||||
|
||||
// WebAuthn procedures
|
||||
webauthn: oc.router({
|
||||
createRegistrationOptions: oc
|
||||
.input(z.object({ email: emailSchema }))
|
||||
.output(
|
||||
z.object({
|
||||
challengeId: z.number(),
|
||||
options: z.any(), // PublicKeyCredentialCreationOptionsJSON
|
||||
})
|
||||
),
|
||||
verifyRegistration: oc
|
||||
.input(
|
||||
z.object({
|
||||
challengeId: z.number(),
|
||||
response: z.any(), // RegistrationResponseJSON
|
||||
})
|
||||
)
|
||||
.output(z.void()),
|
||||
createAuthenticationOptions: oc.output(
|
||||
z.object({
|
||||
challengeId: z.number(),
|
||||
options: z.any(), // PublicKeyCredentialRequestOptionsJSON
|
||||
})
|
||||
),
|
||||
verifyAuthentication: oc
|
||||
.input(
|
||||
z.object({
|
||||
challengeId: z.number(),
|
||||
response: z.any(), // AuthenticationResponseJSON
|
||||
})
|
||||
)
|
||||
.output(z.void()),
|
||||
}),
|
||||
}),
|
||||
|
||||
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()),
|
||||
|
||||
// Authentication settings
|
||||
setPassword: oc.input(setPasswordInputSchema).output(z.void()),
|
||||
listPasskeys: oc.output(z.array(passkeyOutputSchema)),
|
||||
createPasskey: oc
|
||||
.input(z.object({ name: z.string() }))
|
||||
.output(
|
||||
z.object({
|
||||
challengeId: z.number(),
|
||||
options: z.any(), // PublicKeyCredentialCreationOptionsJSON
|
||||
})
|
||||
),
|
||||
renamePasskey: oc
|
||||
.input(z.object({ passkeyId: z.number(), name: z.string() }))
|
||||
.output(z.void()),
|
||||
deletePasskey: oc.input(z.object({ passkeyId: z.number() })).output(z.void()),
|
||||
|
||||
// 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()),
|
||||
}),
|
||||
|
||||
orgs: oc.router({
|
||||
// Org management
|
||||
list: oc.output(z.array(orgOutputSchema)),
|
||||
create: oc
|
||||
.input(createOrgInputSchema)
|
||||
.output(z.object({ slug: z.string() })),
|
||||
get: oc.input(z.object({ slug: slugSchema })).output(orgOutputSchema),
|
||||
update: oc
|
||||
.input(
|
||||
z.object({
|
||||
slug: slugSchema,
|
||||
displayName: z.string().optional(),
|
||||
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()),
|
||||
|
||||
// Members
|
||||
members: oc.router({
|
||||
list: oc
|
||||
.input(z.object({ slug: slugSchema }))
|
||||
.output(z.array(orgMemberOutputSchema)),
|
||||
updateRole: oc.input(updateMemberRoleInputSchema).output(z.void()),
|
||||
remove: oc
|
||||
.input(z.object({ slug: slugSchema, userId: z.number() }))
|
||||
.output(z.void()),
|
||||
}),
|
||||
|
||||
// Invites
|
||||
invites: oc.router({
|
||||
list: oc
|
||||
.input(z.object({ slug: slugSchema }))
|
||||
.output(z.array(orgInviteOutputSchema)),
|
||||
create: oc.input(createInviteInputSchema).output(z.void()),
|
||||
cancel: oc
|
||||
.input(z.object({ slug: slugSchema, inviteId: z.number() }))
|
||||
.output(z.void()),
|
||||
accept: oc.input(z.object({ token: z.string() })).output(z.void()),
|
||||
}),
|
||||
|
||||
// Sites
|
||||
sites: oc.router({
|
||||
list: oc
|
||||
.input(z.object({ slug: slugSchema }))
|
||||
.output(z.array(orgSiteOutputSchema)),
|
||||
}),
|
||||
}),
|
||||
|
||||
admin: oc.router({
|
||||
// Admin org management
|
||||
orgs: oc.router({
|
||||
list: oc.output(z.array(orgOutputSchema)),
|
||||
get: oc.input(z.object({ slug: slugSchema })).output(orgOutputSchema),
|
||||
create: oc
|
||||
.input(adminCreateOrgInputSchema)
|
||||
.output(z.object({ slug: z.string() })),
|
||||
update: oc
|
||||
.input(
|
||||
z.object({
|
||||
slug: slugSchema,
|
||||
displayName: z.string().optional(),
|
||||
logoUrl: z.string().optional(),
|
||||
})
|
||||
)
|
||||
.output(z.void()),
|
||||
delete: oc.input(z.object({ slug: slugSchema })).output(z.void()),
|
||||
listSites: oc
|
||||
.input(z.object({ slug: slugSchema }))
|
||||
.output(z.array(orgSiteOutputSchema)),
|
||||
addSite: oc.input(adminAddSiteInputSchema).output(z.void()),
|
||||
removeSite: oc
|
||||
.input(z.object({ slug: slugSchema, domain: z.string() }))
|
||||
.output(z.void()),
|
||||
}),
|
||||
|
||||
// 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()),
|
||||
}),
|
||||
|
||||
// Admin auth management
|
||||
auth: oc.router({
|
||||
completeLogin: oc.input(z.object({ email: emailSchema })).output(z.void()),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
Reference in New Issue
Block a user