diff --git a/.changeset/v1-me-endpoint-update.md b/.changeset/v1-me-endpoint-update.md new file mode 100644 index 0000000000000..3a40fdb4179ce --- /dev/null +++ b/.changeset/v1-me-endpoint-update.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': minor +'@rocket.chat/rest-typings': minor +--- +Updated /api/v1/me endpoint with AJV schema and TypeScript interface, removed old @openapi block, and cleaned up rest-typings import paths diff --git a/apps/meteor/app/api/server/v1/misc.ts b/apps/meteor/app/api/server/v1/misc.ts index 852351a3c5ded..c81a7146e56e1 100644 --- a/apps/meteor/app/api/server/v1/misc.ts +++ b/apps/meteor/app/api/server/v1/misc.ts @@ -10,6 +10,7 @@ import { isMethodCallAnonProps, isFingerprintProps, isMeteorCall, + validateUnauthorizedErrorResponse, } from '@rocket.chat/rest-typings'; import { escapeHTML } from '@rocket.chat/string-helpers'; import EJSON from 'ejson'; @@ -34,152 +35,67 @@ import { getUserFromParams } from '../helpers/getUserFromParams'; import { getUserInfo } from '../helpers/getUserInfo'; /** - * @openapi - * /api/v1/me: - * get: - * description: Gets user data of the authenticated user - * security: - * - authenticated: [] - * responses: - * 200: - * description: The user data of the authenticated user - * content: - * application/json: - * schema: - * allOf: - * - $ref: '#/components/schemas/ApiSuccessV1' - * - type: object - * properties: - * name: - * type: string - * username: - * type: string - * nickname: - * type: string - * emails: - * type: array - * items: - * type: object - * properties: - * address: - * type: string - * verified: - * type: boolean - * email: - * type: string - * status: - * $ref: '#/components/schemas/UserStatus' - * statusDefault: - * $ref: '#/components/schemas/UserStatus' - * statusText: - * $ref: '#/components/schemas/UserStatus' - * statusConnection: - * $ref: '#/components/schemas/UserStatus' - * bio: - * type: string - * avatarOrigin: - * type: string - * enum: [none, local, upload, url] - * utcOffset: - * type: number - * language: - * type: string - * settings: - * type: object - * properties: - * preferences: - * type: object - * enableAutoAway: - * type: boolean - * idleTimeLimit: - * type: number - * roles: - * type: array - * active: - * type: boolean - * defaultRoom: - * type: string - * customFields: - * type: array - * requirePasswordChange: - * type: boolean - * requirePasswordChangeReason: - * type: string - * services: - * type: object - * properties: - * github: - * type: object - * gitlab: - * type: object - * password: - * type: object - * properties: - * exists: - * type: boolean - * totp: - * type: object - * properties: - * enabled: - * type: boolean - * email2fa: - * type: object - * properties: - * enabled: - * type: boolean - * statusLivechat: - * type: string - * enum: [available, 'not-available'] - * banners: - * type: array - * items: - * type: object - * properties: - * id: - * type: string - * title: - * type: string - * text: - * type: string - * textArguments: - * type: array - * items: {} - * modifiers: - * type: array - * items: - * type: string - * infoUrl: - * type: string - * oauth: - * type: object - * properties: - * authorizedClients: - * type: array - * items: - * type: string - * _updatedAt: - * type: string - * format: date-time - * avatarETag: - * type: string - * default: - * description: Unexpected error - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/ApiFailureV1' + * -------------------------------- + * Types and Schemas + * -------------------------------- */ -API.v1.addRoute( - 'me', - { authRequired: true }, - { - async get() { - const userFields = { ...getBaseUserFields(), services: 1 }; - const user = (await Users.findOneById(this.userId, { projection: userFields })) as IUser; - return API.v1.success(await getUserInfo(user)); - }, - }, +// Keys allowed in fields query +type Keys = + | 'name' | 'username' | 'nickname' | 'emails' | 'status' | 'statusDefault' + | 'statusText' | 'statusConnection' | 'bio' | 'avatarOrigin' | 'utcOffset' + | 'language' | 'settings' | 'idleTimeLimit' | 'roles' | 'active' | 'defaultRoom' + | 'customFields' | 'requirePasswordChange' | 'requirePasswordChangeReason' + | 'services.github' | 'services.gitlab' | 'services.password.bcrypt' + | 'services.totp.enabled' | 'services.email2fa.enabled' | 'statusLivechat' + | 'banners' | 'oauth.authorizedClients' | '_updatedAt' | 'avatarETag'; + +// Query params type +export type MeParams = { fields: Record | Record; user: IUser }; + +// AJV validation schema +const meSchema = { + type: 'object', + properties: {}, + additionalProperties: false, + required: ['fields', 'user'], +}; +export const isMeProps = ajv.compile(meSchema); + +// Response type +export type IMeResponse = { + success: true; +} & Awaited>; + +/** + * -------------------------------- + * /api/v1/me endpoint + * -------------------------------- + */ +export const meEndpoints = API.v1.get( + 'me', + { + authRequired: true, + response: { + 401: validateUnauthorizedErrorResponse, + 200: { + allOf: [ + { $ref: '#/components/schemas/ApiSuccessV1' }, + { $ref: '#/components/schemas/IUser' }, + ], + }, + }, + }, +async function action() { + const userFields = { ...getBaseUserFields(), services: 1 }; + const user = await Users.findOneById(this.userId!, { projection: userFields }); + + if (!user) { + throw new Meteor.Error('error-invalid-user', 'User not found'); + } + + return API.v1.success(await getUserInfo(user)); + }, ); let onlineCache = 0; diff --git a/packages/core-typings/src/Ajv.ts b/packages/core-typings/src/Ajv.ts index eb91852edb6de..ec508fa1c35f5 100644 --- a/packages/core-typings/src/Ajv.ts +++ b/packages/core-typings/src/Ajv.ts @@ -10,6 +10,8 @@ import type { IPermission } from './IPermission'; import type { ISubscription } from './ISubscription'; import type { SlashCommand } from './SlashCommands'; import type { IMediaCall } from './mediaCalls/IMediaCall'; +import type { IUser } from "./IUser"; +import type { IMeResponse } from '../../../apps/meteor/app/api/server/v1/misc'; export const schemas = typia.json.schemas< [ @@ -17,6 +19,8 @@ export const schemas = typia.json.schemas< CallHistoryItem, ICustomUserStatus, SlashCommand, + IUser, + IMeResponse ], '3.0' >(); diff --git a/packages/rest-typings/src/index.ts b/packages/rest-typings/src/index.ts index b0e2dacff7a85..c077796e57969 100644 --- a/packages/rest-typings/src/index.ts +++ b/packages/rest-typings/src/index.ts @@ -28,7 +28,6 @@ import type { InvitesEndpoints } from './v1/invites'; import type { LDAPEndpoints } from './v1/ldap'; import type { LicensesEndpoints } from './v1/licenses'; import type { MailerEndpoints } from './v1/mailer'; -import type { MeEndpoints } from './v1/me'; import type { MiscEndpoints } from './v1/misc'; import type { ModerationEndpoints } from './v1/moderation'; import type { OmnichannelEndpoints } from './v1/omnichannel'; @@ -47,7 +46,6 @@ import type { VideoConferenceEndpoints } from './v1/videoConference'; // eslint-disable-next-line @typescript-eslint/naming-convention export interface Endpoints extends ChannelsEndpoints, - MeEndpoints, ModerationEndpoints, BannersEndpoints, ChatEndpoints, diff --git a/packages/rest-typings/src/v1/me.ts b/packages/rest-typings/src/v1/me.ts deleted file mode 100644 index f9ca62460d74f..0000000000000 --- a/packages/rest-typings/src/v1/me.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { IUser } from '@rocket.chat/core-typings'; - -type Keys = - | 'name' - | 'username' - | 'nickname' - | 'emails' - | 'status' - | 'statusDefault' - | 'statusText' - | 'statusConnection' - | 'bio' - | 'avatarOrigin' - | 'utcOffset' - | 'language' - | 'settings' - | 'idleTimeLimit' - | 'roles' - | 'active' - | 'defaultRoom' - | 'customFields' - | 'requirePasswordChange' - | 'requirePasswordChangeReason' - | 'services.github' - | 'services.gitlab' - | 'services.password.bcrypt' - | 'services.totp.enabled' - | 'services.email2fa.enabled' - | 'statusLivechat' - | 'banners' - | 'oauth.authorizedClients' - | '_updatedAt' - | 'avatarETag'; - -export type MeEndpoints = { - '/v1/me': { - GET: (params?: { fields: Record | Record; user: IUser }) => IUser & { - email?: string; - settings?: { - profile: Record; - preferences: unknown; - }; - avatarUrl: string; - }; - }; -};