Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/v1-me-endpoint-update.md
Original file line number Diff line number Diff line change
@@ -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
173 changes: 28 additions & 145 deletions apps/meteor/app/api/server/v1/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -34,152 +35,34 @@ 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'
* --------------------------------
* /api/v1/me endpoint
* --------------------------------
*/
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));
},
},
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;
Expand Down
2 changes: 2 additions & 0 deletions packages/core-typings/src/Ajv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ 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";

export const schemas = typia.json.schemas<
[
ISubscription | IInvite | ICustomSound | IMessage | IOAuthApps | IPermission | IMediaCall,
CallHistoryItem,
ICustomUserStatus,
SlashCommand,
IUser
],
'3.0'
>();
2 changes: 0 additions & 2 deletions packages/rest-typings/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand Down
46 changes: 0 additions & 46 deletions packages/rest-typings/src/v1/me.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We aren't just removing this; we also need to move the query schema to apps/meteor/app/api/server/v1/misc.ts. I've extracted these from this file. it wasn't located there, but you’ll need to figure it out how

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';
type MeParams = { fields: Record<Keys, 0> | Record<Keys, 1>; user: IUser }
const meSchema = {
	type: 'object',
	properties: {
	},
	additionalProperties: false,
	required: ['fields', 'user'],
};
const isMeProps = ajv.compile<meParams>(MeSchema);

additionally, here in the MeEndpoints type, the params property defines the required query parameters (for GET requests) or the request body (for POST requests). The type following the => arrow specifies exactly what the /v1/me endpoint returns. We use these definitions as our primary reference; the actual API implementation must be strictly matched to return these exact fields

export type MeEndpoints = {
	'/v1/me': {
		GET: (params?: { fields: Record<Keys, 0> | Record<Keys, 1>; user: IUser }) => IUser & {
			email?: string;
			settings?: {
				profile: Record<string, unknown>;
				preferences: unknown;
			};
			avatarUrl: string;
		};
	};
};

This file was deleted.