docs: updated documentation (#476)
Co-authored-by: Monta <monta@HP-ProBook-445-14-inch-G9-Notebook-PC-505aadfc.localdomain>
This commit is contained in:
@@ -7,9 +7,9 @@ description: >-
|
||||
|
||||
# 🧱 Backend and APIs
|
||||
|
||||
## TDrive Services
|
||||
## Twake Drive Services
|
||||
|
||||
As a frontend developer / connector developer to read our APIs, or to understand our data models or just for fun, here is all the details over TDrive backend services.
|
||||
As a frontend developer / connector developer to read our APIs, or to understand our data models or just for fun, here is all the details over Twake Drive backend services.
|
||||
|
||||
If document are empty, check out our Notion documentation: [https://www.notion.so/tdrive/Backend-documentation-e219323593d2401c9887d0e11b2a597b](https://www.notion.so/tdrive/Backend-documentation-e219323593d2401c9887d0e11b2a597b)
|
||||
|
||||
@@ -25,9 +25,9 @@ If document are empty, check out our Notion documentation: [https://www.notion.s
|
||||
|
||||
[notifications/](notifications/README.md))
|
||||
|
||||
## Get started to code in TDrive
|
||||
## Get started to code in Twake Drive
|
||||
|
||||
Want to edit TDrive code ? Congratulation ! You participate in the development of a great product 😃
|
||||
Want to edit Twake Drive code ? Congratulation ! You participate in the development of a great product 😃
|
||||
|
||||
[start-working-into-a-service](intro/start-working-into-a-service.md)
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
description: How applications are managed in TDrive backend
|
||||
description: How applications are managed in Twake Drive backend
|
||||
---
|
||||
|
||||
# 🍎 Applications
|
||||
|
||||
#### Description
|
||||
|
||||
**Applications** is everything related to connectors in TDrive.
|
||||
**Applications** is everything related to connectors in Twake Drive.
|
||||
|
||||
An application is described by an identity sheet containing this information:
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ description: Documents API
|
||||
---
|
||||
|
||||
# Authentication
|
||||
|
||||
<!-- TODO[NOT UP TO DATE] -->
|
||||
All the following routes require the usual authentication header. But you can also use other ways of authentication:
|
||||
|
||||
- For the download routes, you can use a token generated by the `/internal/services/documents/v1/companies/:company_id/download/token` route (see bellow).
|
||||
@@ -299,12 +299,12 @@ Used to create a zip archive containing the requested drive items ( files and fo
|
||||
|
||||
**Auth required** : Yes
|
||||
|
||||
# Tabs (for TDrive)
|
||||
|
||||
# Tabs (for Twake Drive)
|
||||
<!-- TODO[REMOVE SECTION] -->
|
||||
If you want to use the TDrive tabs, you must store the configuration of the tabs in the database.
|
||||
|
||||
<!-- TODO[REMOVE SECTION] -->
|
||||
## Get tab configuration
|
||||
|
||||
<!-- TODO[REMOVE SECTION] -->
|
||||
Get a tab configuration to get the attached folder/document id.
|
||||
|
||||
**URL** : `/internal/services/documents/v1/companies/:company_id/tabs/:id`
|
||||
@@ -326,7 +326,7 @@ Get a tab configuration to get the attached folder/document id.
|
||||
```
|
||||
|
||||
## Set tab configuration
|
||||
|
||||
<!-- TODO[REMOVE SECTION] -->
|
||||
Get a tab configuration to get the attached folder/document id.
|
||||
|
||||
**URL** : `/internal/services/documents/v1/companies/:company_id/tabs/:id`
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
description: File on TDrive
|
||||
description: File on Twake Drive
|
||||
---
|
||||
|
||||
# 📄 Files
|
||||
|
||||
<!-- TODO[NOT UP TO DATE] -->
|
||||
## description
|
||||
|
||||
**Files** is everything related to file upload in TDrive after the migration to Node.js. Note that the TDrive Drive isn't part of this migration because it will be replaced by Linshare.
|
||||
**Files** is everything related to file upload in Twake Drive after the migration to Node.js. Note that the Twake Drive isn't part of this migration because it will be replaced by Linshare.
|
||||
|
||||
TDrive Files upload support chunk upload and file encryption.
|
||||
Twake Drive Files upload support chunk upload and file encryption.
|
||||
|
||||
## Wording
|
||||
|
||||
@@ -18,7 +18,7 @@ TDrive Files upload support chunk upload and file encryption.
|
||||
|
||||
## Encryption
|
||||
|
||||
Files and Storage services in TDrive feature encryption at rest in **aes-256-cbc**.
|
||||
Files and Storage services in Twake Drive feature encryption at rest in **aes-256-cbc**.
|
||||
|
||||
Each file is encrypted with two layers:
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
description: File database models
|
||||
---
|
||||
|
||||
<!-- TODO[NOT UP TO DATE] -->
|
||||
# Database models
|
||||
|
||||
* **files** The main file object in database
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
description: How we use resumable on TDrive
|
||||
description: How we use resumable on Twake Drive
|
||||
---
|
||||
|
||||
# Resumable.js
|
||||
@@ -9,7 +9,7 @@ description: How we use resumable on TDrive
|
||||
## What is Resumable.js
|
||||
|
||||
::: info
|
||||
Using Resumable is not mandatory to upload a file to TDrive. You can do it manually, see REST APIs page.
|
||||
Using Resumable is not mandatory to upload a file to Twake Drive. You can do it manually, see REST APIs page.
|
||||
:::
|
||||
|
||||
It’s a JavaScript library providing multiple simultaneous, stable and resumable uploads via the HTML5 File API.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
---
|
||||
description: Get started with TDrive service development
|
||||
description: Get started with Twake Drive service development
|
||||
---
|
||||
|
||||
# 🛠 TDrive service development
|
||||
# 🛠 Twake Drive service development
|
||||
|
||||
[start-working-into-a-service](start-working-into-a-service.md)
|
||||
|
||||
[create-a-new-tdrive-service](create-a-new-tdrive-service.md)
|
||||
|
||||
[platform/](platform/README.md))
|
||||
[platform/](platform/README.md)
|
||||
|
||||
+17
-17
@@ -1,7 +1,7 @@
|
||||
---
|
||||
description: >-
|
||||
If you are here, you probably have a very great idea for TDrive, like adding a
|
||||
brand new feature into TDrive, maybe a coffee maker service ? ☕️
|
||||
If you are here, you probably have a very great idea for Twake Drive, like adding a
|
||||
brand new feature into Twake Drive, maybe a coffee maker service ? ☕️
|
||||
---
|
||||
|
||||
# Create a new service
|
||||
@@ -19,10 +19,10 @@ In order to illustrate how to create a component, let's create a fake Notificati
|
||||
|
||||
```javascript
|
||||
// File src/services/notification/index.ts
|
||||
import { TDriveService } from "../../core/platform/framework";
|
||||
import { TdriveService } from "../../core/platform/framework";
|
||||
import NotificationServiceAPI from "./api.ts";
|
||||
|
||||
export default class NotificationService extends TDriveService<NotificationServiceAPI> {
|
||||
export default class NotificationService extends TdriveService<NotificationServiceAPI> {
|
||||
version = "1";
|
||||
name = "notification";
|
||||
service: NotificationServiceAPI;
|
||||
@@ -33,15 +33,15 @@ export default class NotificationService extends TDriveService<NotificationServi
|
||||
}
|
||||
```
|
||||
|
||||
1. Our `NotificationService` class extends the generic `TDriveService` class and we defined the `NotificationServiceAPI` as its generic type parameter. It means that in the platform, the other components will be able to retrieve the component from its name and then consume the API defined in the `NotificationServiceAPI` interface and exposed by the `api` method.
|
||||
1. Our `NotificationService` class extends the generic `TdriveService` class and we defined the `NotificationServiceAPI` as its generic type parameter. It means that in the platform, the other components will be able to retrieve the component from its name and then consume the API defined in the `NotificationServiceAPI` interface and exposed by the `api` method.
|
||||
|
||||
We need to create this `NotificationServiceAPI` interface which must extend the `TDriveServiceProvider` from the platform like:
|
||||
We need to create this `NotificationServiceAPI` interface which must extend the `TdriveServiceProvider` from the platform like:
|
||||
|
||||
```javascript
|
||||
// File src/services/notification/api.ts
|
||||
import { TDriveServiceProvider } from "../../core/platform/framework/api";
|
||||
import { TdriveServiceProvider } from "../../core/platform/framework/api";
|
||||
|
||||
export default interface NotificationServiceAPI extends TDriveServiceProvider {
|
||||
export default interface NotificationServiceAPI extends TdriveServiceProvider {
|
||||
|
||||
/**
|
||||
* Send a message to a list of recipients
|
||||
@@ -65,17 +65,17 @@ export class NotificationServiceImpl implements NotificationServiceAPI {
|
||||
}
|
||||
```
|
||||
|
||||
1. `NotificationServiceImpl` now needs to be instanciated from the `NotificationService` class since this is where we choose to keep its reference and expose it. There are several places which can be used to instanciate it, in the constructor itself, or in one of the `TDriveService` lifecycle hooks. The `TDriveService` abstract class has several lifecycle hooks which can be extended by the service implementation for customization pusposes:
|
||||
1. `NotificationServiceImpl` now needs to be instanciated from the `NotificationService` class since this is where we choose to keep its reference and expose it. There are several places which can be used to instanciate it, in the constructor itself, or in one of the `TdriveService` lifecycle hooks. The `TdriveService` abstract class has several lifecycle hooks which can be extended by the service implementation for customization pusposes:
|
||||
2. `public async doInit(): Promise<this>;` Customize the `init` step of the component. This is generally the place where services are instanciated. From this step, you can retrieve services consumed by the current component which have been already initialized by the platform.
|
||||
3. `public async doStart(): Promise<this>;` Customize the `start` step of the component. You have access to all other services which are already started.
|
||||
|
||||
```javascript
|
||||
// File src/services/notification/index.ts
|
||||
import { TDriveService } from "../../core/platform/framework";
|
||||
import { TdriveService } from "../../core/platform/framework";
|
||||
import NotificationServiceAPI from "./api.ts";
|
||||
import NotificationServiceImpl from "./services/api.ts";
|
||||
|
||||
export default class NotificationService extends TDriveService<NotificationServiceAPI> {
|
||||
export default class NotificationService extends TdriveService<NotificationServiceAPI> {
|
||||
version = "1";
|
||||
name = "notification";
|
||||
service: NotificationServiceAPI;
|
||||
@@ -95,12 +95,12 @@ export default class NotificationService extends TDriveService<NotificationServi
|
||||
1. Now that the service is fully created, we can consume it from any other service in the platform. To do this, we rely on Typescript decorators to define the links between components. For example, let's say that the a `MessageService` needs to call the `NotificationServiceAPI`, we can create the link with the help of the `@Consumes` decorator and get a reference to the `NotificationServiceAPI` by calling the `getProvider` on the component context like:
|
||||
|
||||
```javascript
|
||||
import { TDriveService, Consumes } from "../../core/platform/framework";
|
||||
import { TdriveService, Consumes } from "../../core/platform/framework";
|
||||
import MessageServiceAPI from "./providapier";
|
||||
import NotificationServiceAPI from "../notification/api";
|
||||
|
||||
@Consumes(["notification"])
|
||||
export default class MessageService extends TDriveService<MessageServiceAPI> {
|
||||
export default class MessageService extends TdriveService<MessageServiceAPI> {
|
||||
|
||||
public async doInit(): Promise<this> {
|
||||
const notificationService = this.context.getProvider<NotificationServiceAPI>("notification");
|
||||
@@ -115,12 +115,12 @@ export default class MessageService extends TDriveService<MessageServiceAPI> {
|
||||
The platform and services configuration is defined in the `config/default.json` file. It uses [node-config](https://github.com/lorenwest/node-config) under the hood and to configuration file inheritence is supported in the platform.
|
||||
|
||||
The list of services to start is defined in the `services` array like:
|
||||
|
||||
<!-- TODO[NOT UP TO DATE] -->
|
||||
```javascript
|
||||
{
|
||||
"services": ["auth", "user", "channels", "webserver", "websocket", "database", "realtime"]
|
||||
}
|
||||
```
|
||||
```<!-- TODO[NOT UP TO DATE] -->
|
||||
|
||||
Then each service can have its own configuration block which is accessible from its service name i.e. `websocket` service configuration is defined in the `websocket` element like:
|
||||
|
||||
@@ -143,7 +143,7 @@ Then each service can have its own configuration block which is accessible from
|
||||
On the component class side, the configuration object is directly accessible from the `configuration` property like:
|
||||
|
||||
```javascript
|
||||
export default class WebSocket extends TDriveService<WebSocketAPI> {
|
||||
export default class WebSocket extends TdriveService<WebSocketAPI> {
|
||||
async doInit(): Promise<this> {
|
||||
// get the "path" value, defaults to "/socket" if not defined
|
||||
const path = this.configuration.get < string > ("path", "/socket");
|
||||
@@ -166,7 +166,7 @@ After creating a new service, you can add controllers, business services and ent
|
||||
|
||||
## Create a new technical service
|
||||
|
||||
Now you are bringing things a step further, you are going to add new core services in TDrive, like for instance a new database connector or encryption system.
|
||||
Now you are bringing things a step further, you are going to add new core services in Twake Drive, like for instance a new database connector or encryption system.
|
||||
|
||||
Creating a new core service is as easy as creating a functional service. But it must be in `src/core/platform/services` .
|
||||
|
||||
|
||||
@@ -1,247 +1,15 @@
|
||||
---
|
||||
description: >-
|
||||
List of core shared components in TDrive backend, available in
|
||||
List of core shared components in Twake Drive backend, available in
|
||||
src/core/platform/services
|
||||
---
|
||||
|
||||
# Platform/Technical services
|
||||
|
||||
## **Database Technical Service**
|
||||
|
||||
TDrive uses a custom ORM to work with both MongoDB and CassandraDB/ScyllaDB.
|
||||
<!-- TODO[NOT UP TO DATE] -->
|
||||
Twake Drive uses a custom ORM to work with both MongoDB and CassandraDB/ScyllaDB.
|
||||
|
||||
::: info
|
||||
This paragraph is not ready yet, you can contribute to this documentation on our Github!
|
||||
:::
|
||||
|
||||
## **Realtime Technical Service**
|
||||
|
||||
The framework provides simple way to create CRUD Services which will notify clients using Websockets by following some conventions:
|
||||
|
||||
1. The service must implement the `CRUDService` generic interface
|
||||
2. In order to push notification to clients, Typescript decorators must be added on methods \(Create, Update, Delete methods\)
|
||||
|
||||
For example, let's say that we want to implement a CRUD Service for `messages`:
|
||||
|
||||
```javascript
|
||||
import {
|
||||
CRUDService,
|
||||
CreateResult,
|
||||
DeleteResult,
|
||||
UpdateResult,
|
||||
EntityId,
|
||||
} from "@/core/platform/framework/api/crud-service";
|
||||
|
||||
class Message {
|
||||
text: string;
|
||||
createdAt: Date;
|
||||
author: string;
|
||||
}
|
||||
|
||||
class MessageService implements CRUDService<Message> {
|
||||
async create(item: Message): Promise<CreateResult<Message>> {
|
||||
// save the message then return a CreateResult instance
|
||||
}
|
||||
|
||||
async get(id: EntityId): Promise<Message> {
|
||||
// get the message from its ID and return it
|
||||
}
|
||||
|
||||
async update(id: EntityId, item: Message): Promise<UpdateResult<Message>> {
|
||||
// update the message with the given id, patch it with `item` values
|
||||
// then return an instance of UpdateResult
|
||||
}
|
||||
|
||||
async delete(id: EntityId): Promise<DeleteResult<Message>> {
|
||||
// delete the message from its id then return a DeleteResult instance
|
||||
}
|
||||
|
||||
async list(): Promise<Message[]> {
|
||||
// get a list of messages
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
By implementing the CRUD service following the API, we can now add realtime message to clients connected to Websocket on a selected collection of CRUD operations. For example, if we want to just notify when a `message` is created, we just have to add the right decorator on the `create` method like:
|
||||
|
||||
```javascript
|
||||
import { RealtimeCreated } from "@/core/platform/framework/decorators";
|
||||
|
||||
// top and bottom code removed for clarity
|
||||
class MessageService implements CRUDService<Message> {
|
||||
|
||||
@RealtimeCreated<Message>({
|
||||
room: "/messages",
|
||||
path: message => `/messages/${message.id}`
|
||||
})
|
||||
async create(item: Message): Promise<CreateResult<Message>> {
|
||||
// save the message then return a CreateResult instance
|
||||
const created: Message = new Message(/* */);
|
||||
|
||||
return new CreateResult<Message>("message", created)
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The `RealtimeCreated` decorator will intercept the `create` call and will publish an event in an internal event bus with the creation result, the input data and the `"/messages"` path. On the other side of the event bus, an event listener will be in charge of delivering the event to the right Websocket clients as described next.
|
||||
|
||||
The Realtime\* decorators to add on methods to intercept calls and publish data are all following the same API. A decorator takes two parameters as input:
|
||||
|
||||
- First one is the room name to publish the notification to \(`/messages` in the example above\)
|
||||
- Second one is the full path of the resource linked to the action \(`message => /messages/${message.id}` in the example above\)
|
||||
|
||||
Both parameters can take a string or an arrow function as parameter. If arrow function is used, the input parameter will be the result element. By doing this, the paths can be generated dynamically at runtime.
|
||||
|
||||
## **Websocket Technical Service**
|
||||
|
||||
Services annotated as described above automatically publish events to WebSockets. Under the hood, it uses Socket.IO rooms to send events to the right clients.
|
||||
|
||||
**Authentication**
|
||||
|
||||
- Client have to provide a valid JWT token to be able to connect and be authenticated by providing it as string in the `authenticate` event like `{ token: "the jwt token value" }`
|
||||
- If the JWT token is valid, the client will receive a `authenticated` event
|
||||
- If the JWT token is not valid or empty, the client will receive a `unauthorized` event
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
const io = require("socket.io-client");
|
||||
|
||||
// Get a JWT token from the TDrive API first
|
||||
const token =
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOjEsImlhdCI6MTYwMzE5ODkzMn0.NvQoV9KeWuTNzRvzqbJ5uZCQ8Nmi2rCYQzcKk-WsJJ8";
|
||||
const socket = io.connect("http://localhost:3000", { path: "/socket" });
|
||||
|
||||
socket.on("connect", () => {
|
||||
socket
|
||||
.emit("authenticate", { token })
|
||||
.on("authenticated", () => {
|
||||
console.log("User Authenticated");
|
||||
})
|
||||
.on("unauthorized", (err) => {
|
||||
console.log("User is not authorized", err);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("disconnected", () => console.log("Disconnected"));
|
||||
```
|
||||
|
||||
**Joining rooms**
|
||||
|
||||
CRUD operations on resources are pushing events in Socket.io rooms. In order to receive events, clients must subscribe to rooms by sending an `realtime:join` on an authenticated socket with the name of the room to join and with a valid JWT token like `{ name: "room name", token: "the jwt token for this room" }`: Users can not subscribe to arbitratry rooms, they have to be authorized to.
|
||||
|
||||
As a result, the client will receive events:
|
||||
|
||||
- `realtime:join:success` when join is succesful with data containing the name of the linked room like `{ name: "room" }`.
|
||||
- `realtime:join:error` when join failed with data containing the name of the linked room and the error details like `{ name: "room", error: "some error message" }`.
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
const io = require("socket.io-client");
|
||||
|
||||
const token =
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOjEsImlhdCI6MTYwMzE5ODkzMn0.NvQoV9KeWuTNzRvzqbJ5uZCQ8Nmi2rCYQzcKk-WsJJ8";
|
||||
const socket = io.connect("http://localhost:3000", { path: "/socket" });
|
||||
|
||||
socket.on("connect", () => {
|
||||
socket
|
||||
.emit("authenticate", { token })
|
||||
.on("authenticated", () => {
|
||||
// join the /channels room
|
||||
socket.emit("realtime:join", { name: "/channels", token: "tdrive" });
|
||||
socket.on("realtime:join:error", (message) => {
|
||||
// will fire when join does not provide a valid token
|
||||
console.log("Error on join", message);
|
||||
});
|
||||
|
||||
// will be fired on each successful join.
|
||||
// As event based, this event is not linked only to the join above
|
||||
// but to all joins. So you have to dig into the message to know which one
|
||||
// is successful.
|
||||
socket.on("realtime:join:success", (message) => {
|
||||
console.log("Successfully joined room", message.name);
|
||||
});
|
||||
})
|
||||
.on("unauthorized", (err) => {
|
||||
console.log("Unauthorized", err);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("disconnected", () => console.log("Disconnected"));
|
||||
```
|
||||
|
||||
**Leaving rooms**
|
||||
|
||||
The client can leave the room by emitting a `realtime:leave` event with the name of the room given as `{ name: "the room to leave" }`.
|
||||
|
||||
As a result, the client will receive events:
|
||||
|
||||
- `realtime:leave:success` when leave is succesful with data containing the name of the linked room like `{ name: "room" }`.
|
||||
- `realtime:leave:error` when leave failed with data containing the name of the linked room and the error details like `{ name: "room", error: "some error message" }`.
|
||||
|
||||
Note: Asking to leave a room which has not been joined will not fire any error.
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
socket.on("connect", () => {
|
||||
socket.emit("authenticate", { token }).on("authenticated", () => {
|
||||
// leave the "/channels" room
|
||||
socket.emit("realtime:leave", { name: "/channels" });
|
||||
|
||||
socket.on("realtime:leave:error", (message) => {
|
||||
// will fire when join does not provide a valid token
|
||||
console.log("Error on leave", message);
|
||||
});
|
||||
|
||||
// will be fired on each successful leave.
|
||||
// As event based, this event is not linked only to the leave above
|
||||
// but to all leaves. So you have to dig into the message to know which one
|
||||
// is successful.
|
||||
socket.on("realtime:leave:success", (message) => {
|
||||
console.log("Successfully left room", message.name);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**Subscribe to resource events**
|
||||
|
||||
Once the given room has been joined, the client will receive `realtime:resource` events with the resource linked to the event as data:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"action": "created",
|
||||
"room": "/channels",
|
||||
"type": "channel",
|
||||
"path": "/channels/5f905327e3e1626399aaad79",
|
||||
"resource": {
|
||||
"name": "My channel",
|
||||
"id": "5f905327e3e1626399aaad79"
|
||||
}
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```javascript
|
||||
socket.on("connect", () => {
|
||||
socket
|
||||
.emit("authenticate", { token })
|
||||
.on("authenticated", () => {
|
||||
// join the "/channels" room
|
||||
socket.emit("realtime:join", { name: "/channels", token: "tdrive" });
|
||||
|
||||
// will only occur when an action occured on a resource
|
||||
// and if and only if the client joined the room
|
||||
// in which the resource is linked
|
||||
socket.on("realtime:resource", (event) => {
|
||||
console.log("Resource has been ${event.action}", event.resource);
|
||||
});
|
||||
})
|
||||
.on("unauthorized", (err) => {
|
||||
console.log("Unauthorized", err);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
+3
-235
@@ -1,245 +1,13 @@
|
||||
---
|
||||
description: >-
|
||||
List of core shared components in TDrive backend, available in
|
||||
List of core shared components in Twake Drive backend, available in
|
||||
src/core/platform/services
|
||||
---
|
||||
|
||||
# Platform/Technical services
|
||||
|
||||
## **Database Technical Service**
|
||||
|
||||
TDrive uses a custom ORM to work with both MongoDB and CassandraDB/ScyllaDB.
|
||||
<!-- TODO[NOT UP TO DATE] -->
|
||||
Twake Drive uses a custom ORM to work with both MongoDB and CassandraDB/ScyllaDB.
|
||||
|
||||
[database-orm-platform-service](database-orm-platform-service.md)
|
||||
|
||||
## **Realtime Technical Service**
|
||||
|
||||
The framework provides simple way to create CRUD Services which will notify clients using Websockets by following some conventions:
|
||||
|
||||
1. The service must implement the `CRUDService` generic interface
|
||||
2. In order to push notification to clients, Typescript decorators must be added on methods \(Create, Update, Delete methods\)
|
||||
|
||||
For example, let's say that we want to implement a CRUD Service for `messages`:
|
||||
|
||||
```javascript
|
||||
import {
|
||||
CRUDService,
|
||||
CreateResult,
|
||||
DeleteResult,
|
||||
UpdateResult,
|
||||
EntityId,
|
||||
} from "@/core/platform/framework/api/crud-service";
|
||||
|
||||
class Message {
|
||||
text: string;
|
||||
createdAt: Date;
|
||||
author: string;
|
||||
}
|
||||
|
||||
class MessageService implements CRUDService<Message> {
|
||||
async create(item: Message): Promise<CreateResult<Message>> {
|
||||
// save the message then return a CreateResult instance
|
||||
}
|
||||
|
||||
async get(id: EntityId): Promise<Message> {
|
||||
// get the message from its ID and return it
|
||||
}
|
||||
|
||||
async update(id: EntityId, item: Message): Promise<UpdateResult<Message>> {
|
||||
// update the message with the given id, patch it with `item` values
|
||||
// then return an instance of UpdateResult
|
||||
}
|
||||
|
||||
async delete(id: EntityId): Promise<DeleteResult<Message>> {
|
||||
// delete the message from its id then return a DeleteResult instance
|
||||
}
|
||||
|
||||
async list(): Promise<Message[]> {
|
||||
// get a list of messages
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
By implementing the CRUD service following the API, we can now add realtime message to clients connected to Websocket on a selected collection of CRUD operations. For example, if we want to just notify when a `message` is created, we just have to add the right decorator on the `create` method like:
|
||||
|
||||
```javascript
|
||||
import { RealtimeCreated } from "@/core/platform/framework/decorators";
|
||||
|
||||
// top and bottom code removed for clarity
|
||||
class MessageService implements CRUDService<Message> {
|
||||
|
||||
@RealtimeCreated<Message>({
|
||||
room: "/messages",
|
||||
path: message => `/messages/${message.id}`
|
||||
})
|
||||
async create(item: Message): Promise<CreateResult<Message>> {
|
||||
// save the message then return a CreateResult instance
|
||||
const created: Message = new Message(/* */);
|
||||
|
||||
return new CreateResult<Message>("message", created)
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The `RealtimeCreated` decorator will intercept the `create` call and will publish an event in an internal event bus with the creation result, the input data and the `"/messages"` path. On the other side of the event bus, an event listener will be in charge of delivering the event to the right Websocket clients as described next.
|
||||
|
||||
The Realtime\* decorators to add on methods to intercept calls and publish data are all following the same API. A decorator takes two parameters as input:
|
||||
|
||||
- First one is the room name to publish the notification to \(`/messages` in the example above\)
|
||||
- Second one is the full path of the resource linked to the action \(`message => /messages/${message.id}` in the example above\)
|
||||
|
||||
Both parameters can take a string or an arrow function as parameter. If arrow function is used, the input parameter will be the result element. By doing this, the paths can be generated dynamically at runtime.
|
||||
|
||||
## **Websocket Technical Service**
|
||||
|
||||
Services annotated as described above automatically publish events to WebSockets. Under the hood, it uses Socket.IO rooms to send events to the right clients.
|
||||
|
||||
**Authentication**
|
||||
|
||||
- Client have to provide a valid JWT token to be able to connect and be authenticated by providing it as string in the `authenticate` event like `{ token: "the jwt token value" }`
|
||||
- If the JWT token is valid, the client will receive a `authenticated` event
|
||||
- If the JWT token is not valid or empty, the client will receive a `unauthorized` event
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
const io = require("socket.io-client");
|
||||
|
||||
// Get a JWT token from the TDrive API first
|
||||
const token =
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOjEsImlhdCI6MTYwMzE5ODkzMn0.NvQoV9KeWuTNzRvzqbJ5uZCQ8Nmi2rCYQzcKk-WsJJ8";
|
||||
const socket = io.connect("http://localhost:3000", { path: "/socket" });
|
||||
|
||||
socket.on("connect", () => {
|
||||
socket
|
||||
.emit("authenticate", { token })
|
||||
.on("authenticated", () => {
|
||||
console.log("User Authenticated");
|
||||
})
|
||||
.on("unauthorized", (err) => {
|
||||
console.log("User is not authorized", err);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("disconnected", () => console.log("Disconnected"));
|
||||
```
|
||||
|
||||
**Joining rooms**
|
||||
|
||||
CRUD operations on resources are pushing events in Socket.io rooms. In order to receive events, clients must subscribe to rooms by sending an `realtime:join` on an authenticated socket with the name of the room to join and with a valid JWT token like `{ name: "room name", token: "the jwt token for this room" }`: Users can not subscribe to arbitratry rooms, they have to be authorized to.
|
||||
|
||||
As a result, the client will receive events:
|
||||
|
||||
- `realtime:join:success` when join is succesful with data containing the name of the linked room like `{ name: "room" }`.
|
||||
- `realtime:join:error` when join failed with data containing the name of the linked room and the error details like `{ name: "room", error: "some error message" }`.
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
const io = require("socket.io-client");
|
||||
|
||||
const token =
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOjEsImlhdCI6MTYwMzE5ODkzMn0.NvQoV9KeWuTNzRvzqbJ5uZCQ8Nmi2rCYQzcKk-WsJJ8";
|
||||
const socket = io.connect("http://localhost:3000", { path: "/socket" });
|
||||
|
||||
socket.on("connect", () => {
|
||||
socket
|
||||
.emit("authenticate", { token })
|
||||
.on("authenticated", () => {
|
||||
// join the /channels room
|
||||
socket.emit("realtime:join", { name: "/channels", token: "tdrive" });
|
||||
socket.on("realtime:join:error", (message) => {
|
||||
// will fire when join does not provide a valid token
|
||||
console.log("Error on join", message);
|
||||
});
|
||||
|
||||
// will be fired on each successful join.
|
||||
// As event based, this event is not linked only to the join above
|
||||
// but to all joins. So you have to dig into the message to know which one
|
||||
// is successful.
|
||||
socket.on("realtime:join:success", (message) => {
|
||||
console.log("Successfully joined room", message.name);
|
||||
});
|
||||
})
|
||||
.on("unauthorized", (err) => {
|
||||
console.log("Unauthorized", err);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("disconnected", () => console.log("Disconnected"));
|
||||
```
|
||||
|
||||
**Leaving rooms**
|
||||
|
||||
The client can leave the room by emitting a `realtime:leave` event with the name of the room given as `{ name: "the room to leave" }`.
|
||||
|
||||
As a result, the client will receive events:
|
||||
|
||||
- `realtime:leave:success` when leave is succesful with data containing the name of the linked room like `{ name: "room" }`.
|
||||
- `realtime:leave:error` when leave failed with data containing the name of the linked room and the error details like `{ name: "room", error: "some error message" }`.
|
||||
|
||||
Note: Asking to leave a room which has not been joined will not fire any error.
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
socket.on("connect", () => {
|
||||
socket.emit("authenticate", { token }).on("authenticated", () => {
|
||||
// leave the "/channels" room
|
||||
socket.emit("realtime:leave", { name: "/channels" });
|
||||
|
||||
socket.on("realtime:leave:error", (message) => {
|
||||
// will fire when join does not provide a valid token
|
||||
console.log("Error on leave", message);
|
||||
});
|
||||
|
||||
// will be fired on each successful leave.
|
||||
// As event based, this event is not linked only to the leave above
|
||||
// but to all leaves. So you have to dig into the message to know which one
|
||||
// is successful.
|
||||
socket.on("realtime:leave:success", (message) => {
|
||||
console.log("Successfully left room", message.name);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**Subscribe to resource events**
|
||||
|
||||
Once the given room has been joined, the client will receive `realtime:resource` events with the resource linked to the event as data:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"action": "created",
|
||||
"room": "/channels",
|
||||
"type": "channel",
|
||||
"path": "/channels/5f905327e3e1626399aaad79",
|
||||
"resource": {
|
||||
"name": "My channel",
|
||||
"id": "5f905327e3e1626399aaad79"
|
||||
}
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```javascript
|
||||
socket.on("connect", () => {
|
||||
socket
|
||||
.emit("authenticate", { token })
|
||||
.on("authenticated", () => {
|
||||
// join the "/channels" room
|
||||
socket.emit("realtime:join", { name: "/channels", token: "tdrive" });
|
||||
|
||||
// will only occur when an action occured on a resource
|
||||
// and if and only if the client joined the room
|
||||
// in which the resource is linked
|
||||
socket.on("realtime:resource", (event) => {
|
||||
console.log("Resource has been ${event.action}", event.resource);
|
||||
});
|
||||
})
|
||||
.on("unauthorized", (err) => {
|
||||
console.log("Unauthorized", err);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@ description: >-
|
||||
to our channel service ? You are in the right place !
|
||||
---
|
||||
|
||||
# What is a service in TDrive ?
|
||||
# What is a service in Twake Drive ?
|
||||
|
||||
The backend is developed using a **software component approach** in order to compose and adapt the platform based on needs and constraints. The current section describes this approach, and how to extend it by creating new components.
|
||||
|
||||
@@ -25,9 +25,9 @@ The platform currently have some limitations:
|
||||
|
||||
## Discover what is in a service
|
||||
|
||||
To unfold the internal ways of services in TDrive, we will follow a simple request journey into our framework.
|
||||
To unfold the internal ways of services in Twake Drive, we will follow a simple request journey into our framework.
|
||||
|
||||
1. The requests starts from TDrive Frontend or Postman for instance,
|
||||
1. The requests starts from Twake Drive Frontend or Postman for instance,
|
||||
2. it then goes to a controller which validate the request parameters and extract them for the services,
|
||||
3. the services uses the given parameters to get/set entities in database and returns a proper reply.
|
||||
|
||||
|
||||
@@ -3,5 +3,5 @@ description: How users and workspaces are managed in backend
|
||||
---
|
||||
|
||||
# 👥 Users and workspaces
|
||||
|
||||
<!-- TODO[NOT UP TO DATE] -->
|
||||
Document still on notion: [https://www.notion.so/tdrive/Backend-documentation-e219323593d2401c9887d0e11b2a597b](https://www.notion.so/tdrive/Backend-documentation-e219323593d2401c9887d0e11b2a597b)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
description: Get started with TDrive frontend.
|
||||
description: Get started with Twake Drive frontend.
|
||||
---
|
||||
|
||||
# Storybook
|
||||
|
||||
TDrive components are available through our Storybook. You can find it here:
|
||||
<!-- TODO[NOT UP TO DATE] -->
|
||||
Twake Drive components are available through our Storybook. You can find it here:
|
||||
|
||||
[https://web.tdrive.app/storybook/index.html](https://web.tdrive.app/storybook/index.html)
|
||||
|
||||
@@ -4,14 +4,14 @@ description: Here is the list of our middlewares and their usages.
|
||||
|
||||
# 📚 Our stack
|
||||
|
||||
Write an article describing our stack at TDrive composed of:
|
||||
Write an article describing our stack at Twake Drive composed of:
|
||||
|
||||
- In full mode a docker containing: node, react behind nginx, elasticsearch, scylladb, redis, rabbitmq
|
||||
- In simple mode a docker containing: node, react and mongodb for db and search
|
||||
|
||||
### Simple mode
|
||||
|
||||
The simple mode of TDrive messaging app is designed for quick setup and development purposes. It is a more straightforward mode that uses a single container to deploy the components. The technical stack of this mode includes the following components:
|
||||
The simple mode of Twake Drive messaging app is designed for quick setup and development purposes. It is a more straightforward mode that uses a single container to deploy the components. The technical stack of this mode includes the following components:
|
||||
|
||||
- Node: A JavaScript runtime environment used to develop server-side applications.
|
||||
- React: A JavaScript library used to build user interfaces.
|
||||
@@ -21,7 +21,7 @@ The simple mode is great for companies that want a quick and easy setup for thei
|
||||
|
||||
### Full mode
|
||||
|
||||
The full mode of TDrive messaging app is designed to handle large-scale production loads with more than 1000 active users. It uses a container-based approach to deploy the components and ensure scalability. The technical stack of this mode includes the following components:
|
||||
The full mode of Twake Drive messaging app is designed to handle large-scale production loads with more than 1000 active users. It uses a container-based approach to deploy the components and ensure scalability. The technical stack of this mode includes the following components:
|
||||
|
||||
- Node: A JavaScript runtime environment used to develop server-side applications.
|
||||
- React: A JavaScript library used to build user interfaces.
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
---
|
||||
description: Want to translate TDrive ?
|
||||
description: Want to translate Twake Drive ?
|
||||
---
|
||||
|
||||
# 🎭 Translation
|
||||
|
||||
## Translation process
|
||||
|
||||
TDrive is built for everyone. That means we support languages that user want. If we do not already support your language, or you find a mistake in the translation, you have a simply way to do that.
|
||||
Twake Drive is built for everyone. That means we support languages that user want. If we do not already support your language, or you find a mistake in the translation, you have a simply way to do that.
|
||||
|
||||
Send us a mail to this address saying that you want to help us on the translation : [sales@tdrive.app](mailto:sales@tdrive.app). After, you'll have to follow the bellow steps.
|
||||
|
||||
## Join weblate server
|
||||
|
||||
At TDrive, we use [weblate](https://hosted.weblate.org/), a tool that allow you to translate TDrive without working on the code. After you sent an email, we'll invite you to the project. Inside the TDrive project you can find [tdrive-web-frontend](https://hosted.weblate.org/projects/tdrive/tdrive-web-frontend/) that contains all translation for the web version.
|
||||
At Twake Drive, we use [weblate](https://hosted.weblate.org/), a tool that allow you to translate Twake Drive without working on the code. After you sent an email, we'll invite you to the project. Inside the Twake Drive project you can find [tdrive-web-frontend](https://hosted.weblate.org/projects/tdrive/tdrive-web-frontend/) that contains all translation for the web version.
|
||||
|
||||
## Start to translate
|
||||
|
||||
@@ -22,4 +22,4 @@ After choosing the language you want to contribute, you can click on categories
|
||||
|
||||
## Thank you!
|
||||
|
||||
TDrive is build by and for the community. Your commitment is highly appreciate! In reward, we can offer you a year on our SaaS version. You just have to contact us through this email : [sales@tdrive.app](mailto:sales@tdrive.app)
|
||||
Twake Drive is build by and for the community. Your commitment is highly appreciate! In reward, we can offer you a year on our SaaS version. You just have to contact us through this email : [sales@tdrive.app](mailto:sales@tdrive.app)
|
||||
|
||||
Reference in New Issue
Block a user