Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 additions & 0 deletions .changeset/rare-waves-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/rest-typings": minor
---

Add OpenAPI support for the Rocket.Chat users.getAvatarSuggestion API endpoints by migrating to a modern chained route definition syntax and utilizing shared AJV schemas for validation to enhance API documentation and ensure type safety through response validation.
189 changes: 119 additions & 70 deletions apps/meteor/app/api/server/v1/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
isUsersCheckUsernameAvailabilityParamsGET,
isUsersSendConfirmationEmailParamsPOST,
ajv,
validateBadRequestErrorResponse,
validateUnauthorizedErrorResponse,
} from '@rocket.chat/rest-typings';
import { ajv } from '@rocket.chat/rest-typings/src/v1/Ajv';

Check failure on line 25 in apps/meteor/app/api/server/v1/users.ts

View workflow job for this annotation

GitHub Actions / 🔎 Code Check / Code Lint

'ajv' is already defined
import { getLoginExpirationInMs, wrapExceptions } from '@rocket.chat/tools';
import { Accounts } from 'meteor/accounts-base';
import { Match, check } from 'meteor/check';
Expand Down Expand Up @@ -97,20 +100,6 @@
},
);

API.v1.addRoute(
'users.getAvatarSuggestion',
{
authRequired: true,
},
{
async get() {
const suggestions = await getAvatarSuggestionForUser(this.user);

return API.v1.success({ suggestions });
},
},
);

API.v1.addRoute(
'users.update',
{ authRequired: true, twoFactorRequired: true, validateParams: isUsersUpdateParamsPOST },
Expand Down Expand Up @@ -764,72 +753,132 @@
},
);

const usersEndpoints = API.v1.post(
'users.createToken',
{
authRequired: true,
body: ajv.compile<{ userId: string; secret: string }>({
type: 'object',
properties: {
userId: {
type: 'string',
minLength: 1,
},
secret: {
type: 'string',
minLength: 1,
},
},
required: ['userId', 'secret'],
additionalProperties: false,
}),
response: {
200: ajv.compile<{ data: { userId: string; authToken: string } }>({
const usersEndpoints = API.v1
.post(
'users.createToken',
{
authRequired: true,
body: ajv.compile<{ userId: string; secret: string }>({
type: 'object',
properties: {
data: {
type: 'object',
properties: {
userId: {
type: 'string',
minLength: 1,
},
authToken: {
type: 'string',
minLength: 1,
},
},
required: ['userId'],
additionalProperties: false,
userId: {
type: 'string',
minLength: 1,
},
success: {
type: 'boolean',
enum: [true],
secret: {
type: 'string',
minLength: 1,
},
},
required: ['data', 'success'],
additionalProperties: false,
}),
400: ajv.compile({
type: 'object',
properties: {
success: { type: 'boolean', enum: [false] },
error: { type: 'string' },
errorType: { type: 'string' },
},
required: ['success'],
required: ['userId', 'secret'],
additionalProperties: false,
}),
response: {
200: ajv.compile<{ data: { userId: string; authToken: string } }>({
type: 'object',
properties: {
data: {
type: 'object',
properties: {
userId: {
type: 'string',
minLength: 1,
},
authToken: {
type: 'string',
minLength: 1,
},
},
required: ['userId'],
additionalProperties: false,
},
success: {
type: 'boolean',
enum: [true],
},
},
required: ['data', 'success'],
additionalProperties: false,
}),
400: ajv.compile({
type: 'object',
properties: {
success: { type: 'boolean', enum: [false] },
error: { type: 'string' },
errorType: { type: 'string' },
},
required: ['success'],
additionalProperties: false,
}),
},
},
},
async function action() {
const user = await getUserFromParams(this.bodyParams);
async function action() {
const user = await getUserFromParams(this.bodyParams);

const data = await generateAccessToken(user._id, this.bodyParams.secret);
const data = await generateAccessToken(user._id, this.bodyParams.secret);

return API.v1.success({ data });
},
);
return API.v1.success({ data });
},
)
.get(
'users.getAvatarSuggestion',
{
authRequired: true,
response: {
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
200: ajv.compile<{
suggestions: Record<
string,
{
blob: string;
contentType: string;
service: string;
url: string;
}
>;
}>({
type: 'object',
properties: {
success: {
type: 'boolean',
enum: [true],
},
suggestions: {
type: 'object',
additionalProperties: {
type: 'object',
properties: {
blob: {
type: 'string',
},
contentType: {
type: 'string',
},
service: {
type: 'string',
},
url: {
type: 'string',
format: 'uri',
},
},
required: ['blob', 'contentType', 'service', 'url'],
additionalProperties: false,
},
},
},
required: ['success', 'suggestions'],
additionalProperties: false,
}),
},
},
async function action() {
const suggestions = await getAvatarSuggestionForUser(this.user);

return API.v1.success({ suggestions });
},
);

API.v1.addRoute(
'users.getPreferences',
Expand Down
14 changes: 0 additions & 14 deletions packages/rest-typings/src/v1/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,20 +255,6 @@ export type UsersEndpoints = {
};
};

'/v1/users.getAvatarSuggestion': {
GET: () => {
suggestions: Record<
string,
{
blob: string;
contentType: string;
service: string;
url: string;
}
>;
};
};

'/v1/users.checkUsernameAvailability': {
GET: (params: { username: string }) => {
result: boolean;
Expand Down
Loading