v3: shared/ is documented like api/ — deno doc --lint over api/ and shared/ goes from 385 errors to none — every module of shared/ opens with a /** … @module */ block; every export and every field of an exported interface has JSDoc, each request body naming the endpoint that takes it and each response its endpoint; the // comments that described declarations (the mention pattern, the permission union and table, four socket messages, the error message's optimistic-action context) are /** */ now, and the socket protocol's heading comment, which sat on no declaration, is its module doc. The lint wanted explicit types on MENTION_RE and DUMP_MAX_FILE_SIZE_BYTES. Along the way: the chat edit is allowed to moderators too, which the new ChatEditMessage doc says; UpdateUserRequest says that PATCH /api/users/me takes only its description and email; playlist-service's reorderPlaylist @throws is rewrapped. mod.ts and the README no longer call shared/ undocumented. No behaviour changes; 205 backend tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
khannurien
2026-09-27 21:13:07 +00:00
parent d62e4a9883
commit b85a71d86e
18 changed files with 599 additions and 59 deletions

View File

@@ -1,7 +1,17 @@
/**
* Users as the API shows them, their roles, and the bodies and answers of
* sign-up, sign-in, profile edits and invites.
*
* @module
*/
/** What a user may do beyond the basics; see `ROLE_PERMISSIONS`. */
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}. */
export function isRole(value: unknown): value is Role {
return typeof value === "string" &&
(ROLES as readonly string[]).includes(value);
@@ -9,69 +19,111 @@ export function isRole(value: unknown): value is Role {
/** A user as anyone may see them: no password hash, no email. */
export interface PublicUser {
/** The user's id. */
id: string;
/** The user's name, fixed at sign-up. */
username: string;
/** The user's role. */
role: Role;
/** When the user signed up. */
createdAt: Date;
/** When the user last changed, if ever. */
updatedAt?: Date;
/** The avatar's type, undefined without one. */
avatarMime?: string;
/** The user's description of themselves, in Markdown. */
description?: string;
/** The name of whoever invited them. */
invitedByUsername?: string;
}
/** A user as they see themselves: everything but the password hash. */
export interface SelfUser extends PublicUser {
/** The address password resets are sent to. */
email: string;
}
/** A match for the @mention autocomplete. */
export interface UserSuggestion {
/** The user's id. */
id: string;
/** The user's name. */
username: string;
/** The avatar's type, null without one. */
avatarMime: string | null;
}
/** Someone a user invited, directly or down the line. */
export interface Invitee {
/** The user's id. */
id: string;
/** The user's name. */
username: string;
/** The avatar's type, undefined without one. */
avatarMime?: string;
/** The id of whoever invited them, to build the tree. */
invitedById: string;
/** When they signed up. */
createdAt: Date;
}
/** The body of `POST /api/users/login`. */
export interface LoginUserRequest {
/** The user's name. */
username: string;
/** The user's password. */
password: string;
}
/** The body of `POST /api/users/register`. */
export interface RegisterUserRequest {
/** The name to sign up with. */
username: string;
/** The password to sign up with. */
password: string;
/** The invite's token. */
inviteToken: string;
/** The address password resets will be sent to. */
email: string;
}
/**
* Fields of a user to change. `PATCH /api/users/me` takes only `description`
* and `email` from it; the role and the password change through their own
* endpoints.
*/
export interface UpdateUserRequest {
/** The new name. */
username?: string;
/** The new password. */
password?: string;
/** The new role. */
role?: Role;
/** The new description; `null` removes it. */
description?: string | null;
/** The new email address. */
email?: string;
}
/** The session token, plus the media-only one for file and thumbnail URLs. */
export interface AuthTokens {
/** The session token, sent as a bearer token. */
token: string;
/** The token media URLs carry as `?token=`, good for nothing else. */
mediaToken: string;
}
/** What sign-up and sign-in answer: the user and their tokens. */
export interface AuthResponse extends AuthTokens {
/** The signed-in user. */
user: SelfUser;
}
/** `GET /api/users/me/media-token`: a fresh media token. */
export type MediaTokenResponse = Pick<AuthTokens, "mediaToken">;
/** `POST /api/invites`: the new invite's token. */
export interface InviteResponse {
/** The token, which the invite link carries. */
token: string;
}