Routes & Schemas
Shipped routes, hooks, collections, and the common consumer pattern for using thunder-core in your own code.
A quick map of everything thunder-core ships. As always, read the source files for exact request/response shapes and model fields.
Users, Me & Status
| Route file | Responsibility |
|---|---|
routes/users.ts | Admin user management wrapping better-auth admin APIs (create, update, ban, and read-only list/get/count), plus addFcmToken ($addToSet onto the user's fcmTokens, for push notifications) and getUserTenants (the tenants a given user belongs to). |
routes/me.ts | get returns the current authenticated user, plus phone-number verification - see below. |
routes/home.ts | Health / status endpoint. |
Phone-number verification (routes/me.ts)
| Endpoint | Behaviour |
|---|---|
setPhoneNumber | Stores an E.164 number with a 6-digit code and texts it via SMS. Rate-limited to one request per minute by a conditional updateOne - which also means re-submitting the number already on file is rejected. |
verifyPhoneNumber | Checks the code and flips phoneNumberVerified. |
Both require SMS.onSendSms to be configured. thunder-core ships no SMS provider - see SMS.
Shipped Hooks
| Hook | Priority | Responsibility |
|---|---|---|
hooks/essentials.ts | Highest | CORS (preflight + headers, allowing X-TENANT-ID, X-API-KEY and X-PAYLOAD-SIGNATURE) and the DB-backed env fallback (Env.onGetFailed). Its corsHeaders record is exported, so your own hooks and routes can reuse it. |
hooks/guard.ts | Very high | RBAC enforcement on every route via withAuthGuard. |
hooks/requestLogs.ts | Lowest | Logs every response at the appropriate level. |
Schemas / Collections
All models use the framework's mongodb instance with a Zod schema. Read each schema file for exact fields, defaults, and unique indexes (scripts/syncDBIndexes.ts):
schemas/user.ts, schemas/session.ts, schemas/tenant.ts, schemas/tenantMember.ts, schemas/tenantInvite.ts, schemas/accessControl.ts, schemas/accessControlPolicy.ts, schemas/apiKey.ts, schemas/oauthClient.ts, schemas/oauthConsent.ts, schemas/jwk.ts, schemas/env.ts, schemas/references.ts, schemas/wallet.ts, schemas/walletLedger.ts.
resourceGrantSchema (exported from schemas/oauthConsent.ts) maps a tenant id to granted scopes, and is reused by both OAuth consents and API keys to scope access per tenant.
Fields worth knowing
schemas/tenantMember.tscarries an optionalexpiresAt(temporary memberships) and exports theisMembershipExpiredpredicate;schemas/tenantInvite.tscarries the matching optionalmembershipExpiresAt.schemas/user.tsholds the non-better-auth extras:fcmTokens(push tokens),phoneNumber(E.164),phoneNumberVerified,phoneNumberVerificationCodeandphoneNumberVerificationRequestAt.
Some fields carry Zod .meta({ canFilter: true, label: "..." }). That is consumed by Thunder UI to offer filters on a collection - the API itself ignores it. Currently applied in schemas/user.ts (name, email, emailVerified, phoneNumber, role, createdAt) and schemas/walletLedger.ts (type, reference, amount, createdAt, updatedAt).
Common Consumer Pattern
Inside your own routes, gate access and read context like this:
import { Router } from "@/core/http/router.ts";
import { withAuthSession } from "@plugins/Huruf-Tech/thunder-core/utils/withAuthSession.ts";
import { withTenant } from "@plugins/Huruf-Tech/thunder-core/utils/withTenant.ts";
import { Wallet } from "@plugins/Huruf-Tech/thunder-core/lib/wallet.ts";
export default new Router("/api", function billing(router) {
router.post("/charge", function charge() {
return async (req) => {
// Auth + tenant resolution (guard hook already enforced "billing.charge" permission)
const { user } = await withAuthSession(req);
const { tenant } = await withTenant(req);
await Wallet.transfer({
fromTenant: tenant._id,
toTenant: platformTenantId,
currency: "usd",
amount: 500,
user: user._id,
purpose: "subscription",
});
return Response.ok();
};
});
}).group("Billing");To make a new route's permission available, add its scope.action (router fn name + handler fn name) to a policy and attach that policy to the appropriate role(s). The preferred way is ACManager from your own setup code - see Granting a New Permission. Remember the 30-second permission cache.