Skip to content

Commit 92761b2

Browse files
ggazzoclaude
andcommitted
refactor(api): migrate im.messages.others, im.list, im.list.everyone to typed endpoints
Converts the last 3 addRoute calls in im.ts (each with dm/im aliases) to the typed .get() chain pattern with response schemas. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent eea56d1 commit 92761b2

1 file changed

Lines changed: 163 additions & 115 deletions

File tree

  • apps/meteor/app/api/server/v1

apps/meteor/app/api/server/v1/im.ts

Lines changed: 163 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,162 @@ const dmCreateResponseSchema = ajv.compile<{ room: IRoom & { rid: string } }>({
743743
additionalProperties: false,
744744
});
745745

746+
const paginatedMessagesResponseSchema = ajv.compile<{ messages: IMessage[]; offset: number; count: number; total: number }>({
747+
type: 'object',
748+
properties: {
749+
messages: { type: 'array', items: { type: 'object' } },
750+
offset: { type: 'number' },
751+
count: { type: 'number' },
752+
total: { type: 'number' },
753+
success: { type: 'boolean', enum: [true] },
754+
},
755+
required: ['messages', 'offset', 'count', 'total', 'success'],
756+
additionalProperties: false,
757+
});
758+
759+
const paginatedImsResponseSchema = ajv.compile<{ ims: IRoom[]; offset: number; count: number; total: number }>({
760+
type: 'object',
761+
properties: {
762+
ims: { type: 'array', items: { type: 'object' } },
763+
offset: { type: 'number' },
764+
count: { type: 'number' },
765+
total: { type: 'number' },
766+
success: { type: 'boolean', enum: [true] },
767+
},
768+
required: ['ims', 'offset', 'count', 'total', 'success'],
769+
additionalProperties: false,
770+
});
771+
772+
const dmMessagesOthersEndpointsProps = {
773+
authRequired: true as const,
774+
permissionsRequired: ['view-room-administration'],
775+
response: {
776+
200: paginatedMessagesResponseSchema,
777+
400: validateBadRequestErrorResponse,
778+
401: validateUnauthorizedErrorResponse,
779+
403: validateForbiddenErrorResponse,
780+
},
781+
};
782+
783+
const dmMessagesOthersAction = <Path extends string>(_name: Path): TypedAction<typeof dmMessagesOthersEndpointsProps, Path> =>
784+
async function action() {
785+
if (settings.get('API_Enable_Direct_Message_History_EndPoint') !== true) {
786+
throw new Meteor.Error('error-endpoint-disabled', 'This endpoint is disabled', {
787+
route: '/api/v1/im.messages.others',
788+
});
789+
}
790+
791+
const { roomId } = this.queryParams;
792+
if (!roomId) {
793+
throw new Meteor.Error('error-roomid-param-not-provided', 'The parameter "roomId" is required');
794+
}
795+
796+
const room = await Rooms.findOneById<Pick<IRoom, '_id' | 't'>>(roomId, { projection: { _id: 1, t: 1 } });
797+
if (!room || room?.t !== 'd') {
798+
throw new Meteor.Error('error-room-not-found', `No direct message room found by the id of: ${roomId}`);
799+
}
800+
801+
const { offset, count } = await getPaginationItems(this.queryParams);
802+
const { sort, fields, query } = await this.parseJsonQuery();
803+
const ourQuery = Object.assign({}, query, { rid: room._id });
804+
805+
const { cursor, totalCount } = Messages.findPaginated<IMessage>(ourQuery, {
806+
sort: sort || { ts: -1 },
807+
skip: offset,
808+
limit: count,
809+
projection: fields,
810+
});
811+
812+
const [msgs, total] = await Promise.all([cursor.toArray(), totalCount]);
813+
814+
if (!msgs) {
815+
throw new Meteor.Error('error-no-messages', 'No messages found');
816+
}
817+
818+
return API.v1.success({
819+
messages: await normalizeMessagesForUser(msgs, this.userId),
820+
offset,
821+
count: msgs.length,
822+
total,
823+
});
824+
};
825+
826+
const dmListEndpointsProps = {
827+
authRequired: true as const,
828+
response: {
829+
200: paginatedImsResponseSchema,
830+
400: validateBadRequestErrorResponse,
831+
401: validateUnauthorizedErrorResponse,
832+
},
833+
};
834+
835+
const dmListAction = <Path extends string>(_name: Path): TypedAction<typeof dmListEndpointsProps, Path> =>
836+
async function action() {
837+
const { offset, count } = await getPaginationItems(this.queryParams);
838+
const { sort = { name: 1 }, fields } = await this.parseJsonQuery();
839+
840+
// TODO: CACHE: Add Breaking notice since we removed the query param
841+
842+
const subscriptions = await Subscriptions.find({ 'u._id': this.userId, 't': 'd' }, { projection: { rid: 1 } })
843+
.map((item) => item.rid)
844+
.toArray();
845+
846+
const { cursor, totalCount } = Rooms.findPaginated(
847+
{ t: 'd', _id: { $in: subscriptions } },
848+
{
849+
sort,
850+
skip: offset,
851+
limit: count,
852+
projection: fields,
853+
},
854+
);
855+
856+
const [ims, total] = await Promise.all([cursor.toArray(), totalCount]);
857+
858+
return API.v1.success({
859+
ims: await Promise.all(ims.map((room: IRoom) => composeRoomWithLastMessage(room, this.userId))),
860+
offset,
861+
count: ims.length,
862+
total,
863+
});
864+
};
865+
866+
const dmListEveryoneEndpointsProps = {
867+
authRequired: true as const,
868+
permissionsRequired: ['view-room-administration'],
869+
response: {
870+
200: paginatedImsResponseSchema,
871+
400: validateBadRequestErrorResponse,
872+
401: validateUnauthorizedErrorResponse,
873+
403: validateForbiddenErrorResponse,
874+
},
875+
};
876+
877+
const dmListEveryoneAction = <Path extends string>(_name: Path): TypedAction<typeof dmListEveryoneEndpointsProps, Path> =>
878+
async function action() {
879+
const { offset, count }: { offset: number; count: number } = await getPaginationItems(this.queryParams);
880+
const { sort, fields, query } = await this.parseJsonQuery();
881+
882+
const { cursor, totalCount } = Rooms.findPaginated(
883+
{ ...query, t: 'd' },
884+
{
885+
sort: sort || { name: 1 },
886+
skip: offset,
887+
limit: count,
888+
projection: fields,
889+
},
890+
);
891+
892+
const [rooms, total] = await Promise.all([cursor.toArray(), totalCount]);
893+
894+
return API.v1.success({
895+
ims: await Promise.all(rooms.map((room: IRoom) => composeRoomWithLastMessage(room, this.userId))),
896+
offset,
897+
count: rooms.length,
898+
total,
899+
});
900+
};
901+
746902
const dmEndpoints = API.v1
747903
.post('im.delete', dmDeleteEndpointsProps, dmDeleteAction('im.delete'))
748904
.post('dm.delete', dmDeleteEndpointsProps, dmDeleteAction('dm.delete'))
@@ -809,121 +965,13 @@ const dmEndpoints = API.v1
809965
.get('dm.messages', dmMessagesEndpointsProps, dmMessagesAction('dm.messages'))
810966
.get('im.messages', dmMessagesEndpointsProps, dmMessagesAction('im.messages'))
811967
.get('dm.history', dmHistoryEndpointsProps, dmHistoryAction('dm.history'))
812-
.get('im.history', dmHistoryEndpointsProps, dmHistoryAction('im.history'));
813-
814-
API.v1.addRoute(
815-
['dm.messages.others', 'im.messages.others'],
816-
{ authRequired: true, permissionsRequired: ['view-room-administration'] },
817-
{
818-
async get() {
819-
if (settings.get('API_Enable_Direct_Message_History_EndPoint') !== true) {
820-
throw new Meteor.Error('error-endpoint-disabled', 'This endpoint is disabled', {
821-
route: '/api/v1/im.messages.others',
822-
});
823-
}
824-
825-
const { roomId } = this.queryParams;
826-
if (!roomId) {
827-
throw new Meteor.Error('error-roomid-param-not-provided', 'The parameter "roomId" is required');
828-
}
829-
830-
const room = await Rooms.findOneById<Pick<IRoom, '_id' | 't'>>(roomId, { projection: { _id: 1, t: 1 } });
831-
if (!room || room?.t !== 'd') {
832-
throw new Meteor.Error('error-room-not-found', `No direct message room found by the id of: ${roomId}`);
833-
}
834-
835-
const { offset, count } = await getPaginationItems(this.queryParams);
836-
const { sort, fields, query } = await this.parseJsonQuery();
837-
const ourQuery = Object.assign({}, query, { rid: room._id });
838-
839-
const { cursor, totalCount } = Messages.findPaginated<IMessage>(ourQuery, {
840-
sort: sort || { ts: -1 },
841-
skip: offset,
842-
limit: count,
843-
projection: fields,
844-
});
845-
846-
const [msgs, total] = await Promise.all([cursor.toArray(), totalCount]);
847-
848-
if (!msgs) {
849-
throw new Meteor.Error('error-no-messages', 'No messages found');
850-
}
851-
852-
return API.v1.success({
853-
messages: await normalizeMessagesForUser(msgs, this.userId),
854-
offset,
855-
count: msgs.length,
856-
total,
857-
});
858-
},
859-
},
860-
);
861-
862-
API.v1.addRoute(
863-
['dm.list', 'im.list'],
864-
{ authRequired: true },
865-
{
866-
async get() {
867-
const { offset, count } = await getPaginationItems(this.queryParams);
868-
const { sort = { name: 1 }, fields } = await this.parseJsonQuery();
869-
870-
// TODO: CACHE: Add Breaking notice since we removed the query param
871-
872-
const subscriptions = await Subscriptions.find({ 'u._id': this.userId, 't': 'd' }, { projection: { rid: 1 } })
873-
.map((item) => item.rid)
874-
.toArray();
875-
876-
const { cursor, totalCount } = Rooms.findPaginated(
877-
{ t: 'd', _id: { $in: subscriptions } },
878-
{
879-
sort,
880-
skip: offset,
881-
limit: count,
882-
projection: fields,
883-
},
884-
);
885-
886-
const [ims, total] = await Promise.all([cursor.toArray(), totalCount]);
887-
888-
return API.v1.success({
889-
ims: await Promise.all(ims.map((room: IRoom) => composeRoomWithLastMessage(room, this.userId))),
890-
offset,
891-
count: ims.length,
892-
total,
893-
});
894-
},
895-
},
896-
);
897-
898-
API.v1.addRoute(
899-
['dm.list.everyone', 'im.list.everyone'],
900-
{ authRequired: true, permissionsRequired: ['view-room-administration'] },
901-
{
902-
async get() {
903-
const { offset, count }: { offset: number; count: number } = await getPaginationItems(this.queryParams);
904-
const { sort, fields, query } = await this.parseJsonQuery();
905-
906-
const { cursor, totalCount } = Rooms.findPaginated(
907-
{ ...query, t: 'd' },
908-
{
909-
sort: sort || { name: 1 },
910-
skip: offset,
911-
limit: count,
912-
projection: fields,
913-
},
914-
);
915-
916-
const [rooms, total] = await Promise.all([cursor.toArray(), totalCount]);
917-
918-
return API.v1.success({
919-
ims: await Promise.all(rooms.map((room: IRoom) => composeRoomWithLastMessage(room, this.userId))),
920-
offset,
921-
count: rooms.length,
922-
total,
923-
});
924-
},
925-
},
926-
);
968+
.get('im.history', dmHistoryEndpointsProps, dmHistoryAction('im.history'))
969+
.get('dm.messages.others', dmMessagesOthersEndpointsProps, dmMessagesOthersAction('dm.messages.others'))
970+
.get('im.messages.others', dmMessagesOthersEndpointsProps, dmMessagesOthersAction('im.messages.others'))
971+
.get('dm.list', dmListEndpointsProps, dmListAction('dm.list'))
972+
.get('im.list', dmListEndpointsProps, dmListAction('im.list'))
973+
.get('dm.list.everyone', dmListEveryoneEndpointsProps, dmListEveryoneAction('dm.list.everyone'))
974+
.get('im.list.everyone', dmListEveryoneEndpointsProps, dmListEveryoneAction('im.list.everyone'));
927975

928976
export type DmEndpoints = ExtractRoutesFromAPI<typeof dmEndpoints>;
929977

0 commit comments

Comments
 (0)