Skip to content

Instantly share code, notes, and snippets.

@KevLehman
Created December 5, 2022 19:46
Show Gist options
  • Save KevLehman/bcba6cdc3a60fa2d28e06d6f37157925 to your computer and use it in GitHub Desktop.
Save KevLehman/bcba6cdc3a60fa2d28e06d6f37157925 to your computer and use it in GitHub Desktop.
Create endpoint tour
{
"$schema": "https://aka.ms/codetour-schema",
"title": "Relatient - Endpoint creation",
"steps": [
{
"file": "packages/rest-typings/src/v1/omnichannel.ts",
"description": "Welcome!\n\nLet's assume you want to create a new Omnichannel Endpoint.\n\nFirst thing: this is the \"rest typings\" package, here we add all endpoint typings. This file is specific for omnichannel endpoints, so we'll start here",
"line": 2718,
"selection": {
"start": {
"line": 239,
"character": 15
},
"end": {
"line": 239,
"character": 16
}
}
},
{
"file": "packages/rest-typings/src/v1/omnichannel.ts",
"description": "Let's start with a simple one:\n\nThis endpoint is located at `livechat/appearance`. `v1` is the versioning of the endpoint routes. In Rocket.Chat, we're currently at `v1`, so all endpoints will be under `api/v1/xxxxx` to the outside.",
"line": 2719
},
{
"file": "packages/rest-typings/src/v1/omnichannel.ts",
"description": "The typing starts with the HTTP method(s) the route will support. For this case, `livechat/appearance` only handles GET requests. Any request to other endpoints will result in `404` (handled automatically by the router)",
"line": 2720
},
{
"file": "packages/rest-typings/src/v1/omnichannel.ts",
"description": "This is the `return value` of the endpoint. It read as: \"Endpoint `livechat/appearance` doesn't take any params, and returns an object with a key `appearance`, which is an array, whose elements are of type `ISetting`\"",
"line": 2721
},
{
"file": "packages/rest-typings/src/v1/omnichannel.ts",
"description": "When an endpoint accepts request params (like, body or URL params) we pass an argument to the method function. The param can be named anything, but we, as an standard, call it `params` and annotate the typings of it. Typings are stored in this same file.",
"line": 2725
},
{
"file": "packages/rest-typings/src/v1/omnichannel.ts",
"description": "type `LivechatvisitorsInfo` consist of a key, `visitorId` which is an string.\nEven when this provides type safety, for keeping endpoints secure, we need to validate that users don't pass bad things, for example, a `{ __proto: fasfdas }` object. \n\nFor this validation, we use `AJV`.",
"line": 39
},
{
"file": "packages/rest-typings/src/v1/omnichannel.ts",
"description": "`AJV` works on schemas. As a rule, we keep the AJV schema next to the actual type, to facilitate changes and reading. We follow [AJV's reference](https://ajv.js.org/api.html) for doing so. The following schema indicates we want to validate an object with a property `visitorId` of type `string` and that we don't want any other properties on the payload.",
"line": 43
},
{
"file": "packages/rest-typings/src/v1/omnichannel.ts",
"description": "After the schema is complete, we compute the `validator function`, which takes an AJV schema as a param. \n\nWe export this function, as it's what we use on the endpoint for doing the validation. Let's see",
"line": 54
},
{
"file": "apps/meteor/app/livechat/imports/server/rest/visitors.ts",
"description": "Here's where we actually implement the endpoint. `API.v1.addRoute` is the helper function to register a new endpoint. It takes 3 params: \n1. The route handler, without the versioning.\n2. The endpoint metadata (permissions, authentication, validation of params, etc)\n3. The endpoint handlers, by HTTP method.",
"line": 26,
"selection": {
"start": {
"line": 28,
"character": 78
},
"end": {
"line": 28,
"character": 105
}
}
},
{
"file": "apps/meteor/app/livechat/imports/server/rest/visitors.ts",
"description": "Remember, when adding an endpoint, you don't write the `v1` part from typings. This is because it's automatically appended on `API.v1` call.",
"line": 27
},
{
"file": "apps/meteor/app/livechat/imports/server/rest/visitors.ts",
"description": "Here's the endpoint's metadata. \n`authRequired` means endpoint is not public. This is the minimal source of access control. It means: \"only authenticated users can access it\"\n`permissionsRequired` is either an array or an object with HTTP method keys, depending on how fine-grained you like it. When you type it like this (array) it means \"this permissions array should be applied to all endpoints\"\n`validateParams` receives either a function or an object with HTTP method keys, following the same logic as `permissionsRequired`, when typed like this, it applies to all methods\n\nFor our example, you can see we pass `isLivechatVisitorInfoProps` as a param, which means the API router will use the same function to match the body against all HTTP methods defined on the route handler (3rd param). The matching process is automatic, so once defined, it should just work.",
"line": 28
},
{
"file": "apps/meteor/app/livechat/imports/server/rest/visitors.ts",
"description": "Now lets look at the handler. For convention, HTTP method is lowercase always. Also, handlers can be `async` so feel free to use async/await",
"line": 30
},
{
"file": "apps/meteor/app/livechat/imports/server/rest/visitors.ts",
"description": "Here's the business logic, as a convention, we abstract the logic of the endpoints on other files, let's go there.",
"line": 31
},
{
"file": "apps/meteor/app/livechat/server/api/lib/visitors.ts",
"description": "here's the actual service definition. As you can see, this function is \"dumb\" as it doesn't check for permissions/validates params. It trusts the caller to do this before calling it.",
"line": 7
},
{
"file": "apps/meteor/app/livechat/server/api/lib/visitors.ts",
"description": "This is a `model` call. Models are the abstraction layer we put to interact with database logic. Each model is imported from the `@rocket.chat/models` package. All queries to interact with MongoDB should be done on models. \nLets see a model definition",
"line": 8
},
{
"file": "apps/meteor/server/models/raw/LivechatVisitors.ts",
"description": "Here's the \"definition\" of the `LivechatVisitors` model we're using. As you see, we call them `raw`. This is because we use 2 types of models:\n- Meteor models, the ones the framework provides, which has other capabilities, like realtime apis and sync behavior (using fibers)\n- Raw models, or models that use the raw mongo connector to interact with the DB.\n\nAs we don't rely much on Meteor capabilities, we're now using more `raw` models like this.",
"line": 21
},
{
"file": "apps/meteor/server/models/LivechatVisitors.ts",
"description": "After defining your model, you `register` it. This allows us to use the service in the codebase.\n\nWhen registering, we instantiate the model, passing the DB connection object as param.\n\n(Unless you create a new DB collection, you won't need to register models. This is something done at server boot)",
"line": 6
},
{
"file": "packages/model-typings/src/models/ILivechatVisitorsModel.ts",
"description": "However, something you would probably do is adding new capabilities to existing models.\n\nFor this case, we need also to provide typings. We have some helper types coming from Mongo package for `cursors` and `results` that may come handy.\n\nAs a note: prefer reusing existing types rather than creating them for specific scenarios. You can find more typings for Rocket.Chat on the `@rocket.chat/core-typings` package.",
"line": 6
},
{
"file": "apps/meteor/app/livechat/server/api/lib/visitors.ts",
"description": "Coming back to here: adding proper validations is key. In this case, we're throwing an error in case the `visitorId` doesn't match an actual visitor. This provides more safety on API usage and prevents using null/undefined values on processing.\n\nDon't be afraid of throwing errors, the API handler can manage them.\n\nJust one small thing: _always_ make sure to await your promises. Leaving promises just going without handling could produce unintended results or server crash.",
"line": 9
},
{
"file": "apps/meteor/app/livechat/server/api/lib/visitors.ts",
"description": "After that validation, we return the value. Make sure to always have the proper types!\n\nAs a rule of our coding, we avoid using `any` as a type, or type things as `{[k: string]: any}` when a better type may be created.",
"line": 13
},
{
"file": "apps/meteor/app/livechat/imports/server/rest/visitors.ts",
"description": "When your function ends, always remember to `return API.v1.success()` with your data. This function wraps the call with some metadata we need to know if the result is valid or not.\n\nSuccess means, well, the request was good. And will produce a 200 status.\nThere's also helpers for `error` `unauthenticated` `notFound` so you use them at will.",
"line": 32
},
{
"file": "apps/meteor/app/livechat/imports/server/rest/visitors.ts",
"description": "And with it, that's how endpoints are done @ Rocket.Chat. Hope this documentation helpsyou to get better understanding on it.",
"line": 33
}
],
"ref": "develop"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment