132 lines
4.0 KiB
TypeScript
132 lines
4.0 KiB
TypeScript
/**
|
|
* Types and guards the rows of `users`, and converts them to the {@link User}
|
|
* that `user-service.ts` returns. A route turns a `User` into what it may send
|
|
* with {@link toPublicUser} or {@link toSelfUser}.
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
import type { SQLOutputValue } from "node:sqlite";
|
|
import {
|
|
isRole,
|
|
type PublicUser,
|
|
type Role,
|
|
type SelfUser,
|
|
} from "../../shared/user.ts";
|
|
|
|
/**
|
|
* Returned by the lookups of `user-service.ts`. It must never be sent as is:
|
|
* it carries the password hash.
|
|
*/
|
|
export interface User extends SelfUser {
|
|
/** From `hashPassword`, as `hash.salt`. */
|
|
passwordHash: string;
|
|
}
|
|
|
|
/**
|
|
* Returns what anyone may see of `user`, without the hash and the email. Every
|
|
* route that sends another user's profile goes through it.
|
|
*/
|
|
export function toPublicUser(
|
|
{ passwordHash: _, email: _email, ...user }: User,
|
|
): PublicUser {
|
|
return user;
|
|
}
|
|
|
|
/**
|
|
* Returns what `user` sees of themselves, without the hash, for the routes
|
|
* that answer the caller's own account.
|
|
*/
|
|
export function toSelfUser({ passwordHash: _, ...user }: User): SelfUser {
|
|
return user;
|
|
}
|
|
|
|
/**
|
|
* Selected by `user-service.ts` and `follow-service.ts`, and converted by
|
|
* {@link userRowToApi}. It leaves out `chat_read_at` and `tokens_valid_after`,
|
|
* which only the chat's read state and `getSessionState` query.
|
|
*/
|
|
export interface UserRow {
|
|
id: string;
|
|
username: string;
|
|
password_hash: string;
|
|
role: Role;
|
|
created_at: string;
|
|
/** Bumped by any profile or avatar change. */
|
|
updated_at: string | null;
|
|
/** Null without an avatar. */
|
|
avatar_mime: string | null;
|
|
/** Markdown. */
|
|
description: string | null;
|
|
/** Null for the seeded admin, who had no inviter. */
|
|
invited_by: string | null;
|
|
/**
|
|
* Joined from the inviter's row rather than stored, and undefined when the
|
|
* query doesn't join it. The guard doesn't check it.
|
|
*/
|
|
invited_by_username: string | null;
|
|
email: string;
|
|
[key: string]: SQLOutputValue;
|
|
}
|
|
|
|
/**
|
|
* Checks whether `obj`, a row selected with the columns of {@link UserRow}, is
|
|
* one.
|
|
*/
|
|
export function isUserRow(obj: unknown): obj is UserRow {
|
|
return !!obj &&
|
|
typeof obj === "object" &&
|
|
"id" in obj && typeof obj.id === "string" &&
|
|
"username" in obj && typeof obj.username === "string" &&
|
|
"password_hash" in obj && typeof obj.password_hash === "string" &&
|
|
"role" in obj && isRole(obj.role) &&
|
|
"created_at" in obj && typeof obj.created_at === "string" &&
|
|
"updated_at" in obj &&
|
|
(typeof obj.updated_at === "string" || obj.updated_at === null) &&
|
|
"avatar_mime" in obj &&
|
|
(typeof obj.avatar_mime === "string" || obj.avatar_mime === null) &&
|
|
"description" in obj &&
|
|
(typeof obj.description === "string" || obj.description === null) &&
|
|
"invited_by" in obj &&
|
|
(typeof obj.invited_by === "string" || obj.invited_by === null) &&
|
|
"email" in obj && typeof obj.email === "string";
|
|
}
|
|
|
|
/** Converts a row to a {@link User}, turning nulls into undefined. */
|
|
export function userRowToApi(row: UserRow): User {
|
|
return {
|
|
id: row.id,
|
|
username: row.username,
|
|
passwordHash: row.password_hash,
|
|
role: row.role,
|
|
createdAt: new Date(row.created_at),
|
|
updatedAt: row.updated_at ? new Date(row.updated_at) : undefined,
|
|
avatarMime: row.avatar_mime ?? undefined,
|
|
description: row.description ?? undefined,
|
|
invitedByUsername: typeof row.invited_by_username === "string"
|
|
? row.invited_by_username
|
|
: undefined,
|
|
email: row.email,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Converts a {@link User} back to a row, for `updateUser`. A `User` has no
|
|
* inviter id, so `invited_by` comes out null: don't write it back.
|
|
*/
|
|
export function userApiToRow(user: User): UserRow {
|
|
return {
|
|
id: user.id,
|
|
username: user.username,
|
|
password_hash: user.passwordHash,
|
|
role: user.role,
|
|
created_at: user.createdAt.toISOString(),
|
|
updated_at: user.updatedAt?.toISOString() ?? null,
|
|
avatar_mime: user.avatarMime ?? null,
|
|
description: user.description ?? null,
|
|
invited_by: null,
|
|
invited_by_username: null,
|
|
email: user.email,
|
|
};
|
|
}
|