/** * Types users as the API sends them, their roles, and the bodies and answers * of the `/api/users` and `/api/invites` endpoints, for the server and the * client alike. * * @module */ /** * Sets what a user may do beyond the basics, through `ROLE_PERMISSIONS` * (`shared/permissions.ts`). An admin changes it with * `PATCH /api/users/:userId/role`. */ export type Role = "user" | "moderator" | "admin"; /** Every {@link Role}, least to most privileged. */ export const ROLES: readonly Role[] = ["user", "moderator", "admin"]; /** * Checks whether `value` is a {@link Role}, as the role endpoint and the row * guards must before trusting one. */ export function isRole(value: unknown): value is Role { return typeof value === "string" && (ROLES as readonly string[]).includes(value); } /** * Returned by every endpoint that shows a user to others: profiles, the users * one follows, search results, and the `user_updated` socket message. The * server makes it with `toPublicUser`, which drops the email. */ export interface PublicUser { id: string; /** Fixed at sign-up. */ username: string; role: Role; createdAt: Date; /** * Bumped by any profile or avatar change. The client uses it to reload the * avatar. */ updatedAt?: Date; /** Undefined without an avatar. */ avatarMime?: string; /** Markdown. */ description?: string; invitedByUsername?: string; } /** * Returned to a user about themselves: by sign-up and sign-in in * {@link AuthResponse}, by `GET /api/users/me`, and by an avatar upload. */ export interface SelfUser extends PublicUser { /** Where password reset links go. It belongs to this account only. */ email: string; } /** * Returned by `GET /api/users/search` to the editor's @mention autocomplete. */ export interface UserSuggestion { id: string; username: string; /** Null without an avatar. */ avatarMime: string | null; } /** * Returned, as a flat list in sign-up order, by * `GET /api/users/:username/invitees`. The profile page nests it into a tree * by `invitedById`. */ export interface Invitee { id: string; username: string; /** Undefined without an avatar. */ avatarMime?: string; invitedById: string; createdAt: Date; } /** Sent to `POST /api/users/login` by the login page. */ export interface LoginUserRequest { username: string; password: string; } /** Sent to `POST /api/users/register` by the register page. */ export interface RegisterUserRequest { username: string; password: string; /** From the invite link the register page was opened with. */ inviteToken: string; email: string; } /** * Passed to `updateUser` by the users routes and the password reset. Of a * client's body, `PATCH /api/users/me` takes only `description` and `email`; * the role and the password change through their own endpoints. */ export interface UpdateUserRequest { username?: string; password?: string; role?: Role; /** An empty string clears it. Null, like undefined, keeps it. */ description?: string | null; email?: string; } /** * Returned by sign-up, sign-in and a password change. The client keeps both * tokens with its session. */ export interface AuthTokens { /** Sent as `Authorization: Bearer …`, and as `?token=` to the WebSocket. */ token: string; /** * Carried as `?token=` by the URLs of private media. It can read media and * nothing else. */ mediaToken: string; } /** Returned by sign-up and sign-in. */ export interface AuthResponse extends AuthTokens { user: SelfUser; } /** * Returned by `GET /api/users/me/media-token` to a session from before media * tokens, which has none. */ export type MediaTokenResponse = Pick; /** * Returned by `POST /api/invites` to the profile page, which builds the invite * link from `token`. */ export interface InviteResponse { token: string; }