Skip to content

Commit 21eb183

Browse files
authored
Merge pull request #53 from Callgent/blog
Blog
2 parents 37a0a44 + bfd402d commit 21eb183

21 files changed

Lines changed: 488 additions & 375 deletions

blog/2024-03-16-nestjs+prisma-transaction-propagation-and-test-rollback-and-tenancy.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,10 @@ RLS is only applicable in `postgreSQL`
170170
171171
### How to implement
172172
173-
1. in `schema.prisma` file, set db column `tenant_id` default value to context variable `tenancy.tenantId`,
173+
1. in `schema.prisma` file, set db column `tenant_id` default value to context variable `tenancy.tenantPk`,
174174
175175
```prisma
176-
tenant_id Int @default(dbgenerated("(current_setting('tenancy.tenantId'))::int"))
176+
tenant_id Int @default(dbgenerated("(current_setting('tenancy.tenantPk'))::int"))
177177
```
178178
179179
2. enable postgres row level security(RLS), so that we can filter data by `tenant_id` automatically:
@@ -183,13 +183,13 @@ RLS is only applicable in `postgreSQL`
183183
3. on each request, preset `tenant_id` into `cls` context, refer to [auth-logined.listener.ts](https://github.com/Callgent/callgent-api/blob/main/src/users/listeners/auth-logined.listener.ts),
184184
185185
```typescript
186-
cls.set('TENANT_ID', curentUser.tenantId);
186+
cls.set('TENANT_ID', curentUser.tenantPk);
187187
```
188188
189189
4. extend `PrismaClient` to set `tenant_id` before any query, refer to [prisma-tenancy.provider.ts](https://github.com/Callgent/callgent-api/blob/main/src/infra/repo/tenancy/prisma-tenancy.provider.ts),
190190
191191
```sql
192-
SELECT set_config('tenancy.tenantId', cls.get('TENANT_ID') ...
192+
SELECT set_config('tenancy.tenantPk', cls.get('TENANT_ID') ...
193193
```
194194
195195
5. if you want to bypass rls, for example, by admin, or looking up the logon user to determine their tenant ID:
@@ -206,10 +206,10 @@ RLS is only applicable in `postgreSQL`
206206
207207
### how it works
208208
209-
1. `tenantId` is set into `cls` context on each request from current user.
210-
2. `PrismaClient` is extended on `$allOperations` to set `tenantId` into db variable `tenancy.tenantId` before any query.
211-
3. postgreSQL RLS is enabled, so that all queries will be filtered by `tenancy.tenantId` automatically.
212-
4. on db `insert` operation, `tenancy.tenantId` is set into `tenant_id` column as default value.
209+
1. `tenantPk` is set into `cls` context on each request from current user.
210+
2. `PrismaClient` is extended on `$allOperations` to set `tenantPk` into db variable `tenancy.tenantPk` before any query.
211+
3. postgreSQL RLS is enabled, so that all queries will be filtered by `tenancy.tenantPk` automatically.
212+
4. on db `insert` operation, `tenancy.tenantPk` is set into `tenant_id` column as default value.
213213
214214
## Integrate them all
215215
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
---
2+
sidebar_position: 1
3+
title: Design the Service
4+
description: Callgent is yet another AI programming tool besides Copilot, UI generator, and bug fixer, etc.
5+
keywords: [user as a service]
6+
---
7+
8+
Let's create a recruiting callgent service, to receive and process job applications.
9+
10+
:::tip
11+
There are 2 benefits for using Callgent to create this service:
12+
13+
1. We can build this service without any programming or coding, since requests are processed by real humans.
14+
2. The service can be integrated into any existing systems via API, pages, etc.
15+
16+
:::
17+
18+
## Design Service API
19+
20+
Firstly, you may send the below prompt to some AI tools like [Mistral](https://chat.mistral.ai) to generate an OpenAPI document:
21+
22+
```markdown
23+
Generate an OpenAPI documentation in json format for `Recruiting Service`,
24+
including the following functions:
25+
- `List/View job positions`
26+
- `Apply for a position`
27+
```
28+
29+
Then the `recruiting-service-api.json` is generated:
30+
<details>
31+
<summary>Click to view <i>recruiting-service-api.json</i></summary>
32+
33+
```json {15-17,50-52,76-78}
34+
{
35+
"openapi": "3.0.0",
36+
"info": {
37+
"title": "Recruiting Service API",
38+
"description": "API for managing job positions and applications.",
39+
"version": "1.0.0"
40+
},
41+
"servers": [
42+
{
43+
"url": "http://api.recruitingservice.com/v1",
44+
"description": "Production server"
45+
}
46+
],
47+
"paths": {
48+
"/positions": {
49+
"get": {
50+
"summary": "List all job positions",
51+
"description": "Retrieve a list of all available job positions.",
52+
"operationId": "listPositions",
53+
"responses": {
54+
"200": {
55+
"description": "A list of job positions",
56+
"content": {
57+
"application/json": {
58+
"schema": {
59+
"type": "array",
60+
"items": {
61+
"$ref": "#/components/schemas/JobPosition"
62+
}
63+
}
64+
}
65+
}
66+
},
67+
"500": {
68+
"description": "Internal Server Error"
69+
}
70+
}
71+
}
72+
},
73+
"/positions/{positionId}": {
74+
"get": {
75+
"summary": "View a specific job position",
76+
"description": "Retrieve details of a specific job position by its ID.",
77+
"operationId": "viewPosition",
78+
"parameters": [
79+
{
80+
"name": "positionId",
81+
"in": "path",
82+
"required": true,
83+
"schema": {
84+
"type": "string"
85+
},
86+
"description": "The ID of the job position to retrieve"
87+
}
88+
],
89+
"responses": {
90+
"200": {
91+
"description": "Details of the job position",
92+
"content": {
93+
"application/json": {
94+
"schema": {
95+
"$ref": "#/components/schemas/JobPosition"
96+
}
97+
}
98+
}
99+
},
100+
"404": {
101+
"description": "Job position not found"
102+
},
103+
"500": {
104+
"description": "Internal Server Error"
105+
}
106+
}
107+
}
108+
},
109+
"/positions/{positionId}/apply": {
110+
"post": {
111+
"summary": "Apply for a position",
112+
"description": "Submit an application for a specific job position.",
113+
"operationId": "applyForPosition",
114+
"parameters": [
115+
{
116+
"name": "positionId",
117+
"in": "path",
118+
"required": true,
119+
"schema": {
120+
"type": "string"
121+
},
122+
"description": "The ID of the job position to apply for"
123+
}
124+
],
125+
"requestBody": {
126+
"required": true,
127+
"content": {
128+
"application/json": {
129+
"schema": {
130+
"$ref": "#/components/schemas/Application"
131+
}
132+
}
133+
}
134+
},
135+
"responses": {
136+
"201": {
137+
"description": "Application submitted successfully"
138+
},
139+
"400": {
140+
"description": "Invalid input"
141+
},
142+
"404": {
143+
"description": "Job position not found"
144+
},
145+
"500": {
146+
"description": "Internal Server Error"
147+
}
148+
}
149+
}
150+
}
151+
},
152+
"components": {
153+
"schemas": {
154+
"JobPosition": {
155+
"type": "object",
156+
"properties": {
157+
"id": {
158+
"type": "string",
159+
"description": "Unique identifier for the job position"
160+
},
161+
"title": {
162+
"type": "string",
163+
"description": "Title of the job position"
164+
},
165+
"description": {
166+
"type": "string",
167+
"description": "Description of the job position"
168+
},
169+
"location": {
170+
"type": "string",
171+
"description": "Location of the job position"
172+
},
173+
"requirements": {
174+
"type": "array",
175+
"items": {
176+
"type": "string"
177+
},
178+
"description": "List of requirements for the job position"
179+
},
180+
"createdAt": {
181+
"type": "string",
182+
"format": "date-time",
183+
"description": "Timestamp when the job position was created"
184+
},
185+
"updatedAt": {
186+
"type": "string",
187+
"format": "date-time",
188+
"description": "Timestamp when the job position was last updated"
189+
}
190+
}
191+
},
192+
"Application": {
193+
"type": "object",
194+
"properties": {
195+
"applicantName": {
196+
"type": "string",
197+
"description": "Name of the applicant"
198+
},
199+
"email": {
200+
"type": "string",
201+
"format": "email",
202+
"description": "Email address of the applicant"
203+
},
204+
"resume": {
205+
"type": "string",
206+
"format": "binary",
207+
"description": "Resume of the applicant"
208+
},
209+
"coverLetter": {
210+
"type": "string",
211+
"description": "Cover letter of the applicant"
212+
},
213+
"appliedAt": {
214+
"type": "string",
215+
"format": "date-time",
216+
"description": "Timestamp when the application was submitted"
217+
}
218+
}
219+
}
220+
}
221+
}
222+
}
223+
```
224+
225+
</details>
226+
227+
There are 3 APIs are designed in `recruiting-service-api.json`.
228+
229+
Next we'll import this API definition into a new callgent,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
sidebar_position: 2
3+
title: Import into a Callgent, Then Invoke
4+
description: .
5+
keywords: [user as a service]
6+
---
7+
8+
import CreateEndpoints from "@site/src/components/user-as-a-service/create-endpoints"
9+
import CreateCallgent from "@site/src/components/user-as-a-service/create-callgent"
10+
import ImportApi from "@site/src/components/user-as-a-service/import-api"
11+
import Email from "@site/src/components/quick-start/email"
12+
import CascadingMenu from "@site/src/components/tree"
13+
14+
We are ready to create the recruiting callgent, then import the API document into the callgent.
15+
16+
## Create a new callgent
17+
18+
We can use the following widget to create a new callgent,
19+
20+
{/* <pre><CreateCallgent /></pre> */}
21+
<CascadingMenu adaptorKey="Email" name="Recruitment" />
22+
23+
A callgent contains:
24+
25+
- a list of `Client Endpoints`
26+
to accept calling requests to the callgent,
27+
- a list of `Server Endpoints`
28+
to forward the calling requests to the actual services,
29+
- a list of `Event Endpoints`
30+
callback hooks from actual service.
31+
32+
## Import the API document
33+
34+
After creating the callgent, we may create an `SEP`, then import the API document into it:
35+
36+
1. Click `Create SEP` button,
37+
2. Here we create an Email SEP, fill in your Email address,
38+
Every incoming call request will be sent to this Email address
39+
3. Click `Import API` button to import the API document into the SEP.
40+
41+
<figure>
42+
![Create an SEP, then import API](./create-sep.png "Create an SEP")
43+
</figure>
44+
45+
Creating recruiting callgent is all done.
46+
47+
## Invoking the callgent
48+
49+
Now you may call this new service anywhere by every `CEP` as show in the callgent,
50+
51+
### Invoking REST API:
52+
53+
try invoke `List all job positions` API as follows,
54+
55+
```shell
56+
curl https://api.callgent.com/api/callgents/{callgentId}/{sepId}/invoke/api/positions
57+
```
58+
59+
### Sending Email
60+
61+
or by sending an Email to
62+
<a href="mailto:callgent+{yourCepId}@my.callgent.com?subject=Please%20Respond%20Calling%20to%20`List%20all%20job%20positions`&body=I%20am%20requesting%20to%20call%20`List%20all%20job%20positions`.%20Please%20respond%20to%20this%20Email%20with%20the%20calling%20request%20details.">callgent+yourCepId#my.callgent.com</a>
63+
64+
### More calling endpoints
65+
66+
You may create more `CEP`s to accept calling requests to the callgent, e.g. `SIP`, `WebRTC`, `SMS`, etc.

0 commit comments

Comments
 (0)