Skip to content

Commit 312c0a3

Browse files
lauravikanisLemonaOna
authored andcommitted
✨ Add types for integration entity concept
1 parent 4aff412 commit 312c0a3

File tree

9 files changed

+150
-56
lines changed

9 files changed

+150
-56
lines changed

README.md

Lines changed: 73 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,32 @@
11
# sipgate Integration Bridge Framework
22

3-
This is the sipgate Integration Bridge framework to integrate sipgate apps with external services. It provides a unified way to connect apps to any provider of external data management, like contacts or calendar events.
3+
This is the sipgate Integration Bridge framework to integrate sipgate apps with external services. It provides a unified
4+
way to connect apps to any provider of external data management, like contacts or calendar events.
5+
6+
## Developing locally
7+
8+
To test your changes to the integration-bridge framework locally, follow these steps:
9+
10+
* Run `npm run dev` in sipgate-integration-bridge repo
11+
* Run `npm link @sipgate/integration-bridge` in *-integration-bridge-repo
12+
* Add the following to your `tsconfig.json`:
13+
```json
14+
{
15+
"compilerOptions": {
16+
"paths": {
17+
"@sipgate/integration-bridge": ["node_modules/@sipgate/integration-bridge/src"]
18+
}
19+
}
20+
}
21+
```
422

523
## Publishing a version
624
To publish a new framework version, simply create a new tag and push it:
725
```
26+
827
npm version <minor|patch>
928
git push --follow-tags
29+
1030
```
1131
This will automatically publish the package in the npm registry via the CI pipeline.
1232

@@ -31,27 +51,27 @@ The minimum adapter implements the `getContacts` method:
3151
const bridge = require("@sipgate/integration-bridge");
3252
const fetch = require("node-fetch");
3353

34-
const { ServerError } = bridge;
54+
const {ServerError} = bridge;
3555

3656
const adapter = {
37-
getContacts: async ({ apiKey, apiUrl }) => {
38-
// Fetch contacts using apiUrl and apiKey
39-
const response = await fetch(`${apiUrl}/api/contacts`, {
40-
headers: { Authorization: `Bearer ${apiKey}` },
41-
});
42-
43-
if (response.status === 401) {
44-
throw new ServerError(401, "Unauthorized");
45-
}
46-
47-
if (!response.ok) {
48-
throw new ServerError(500, "Could not fetch contacts");
49-
}
50-
51-
// TODO: Convert contacts to the structure below
52-
const contacts = await response.json();
53-
return contacts;
54-
},
57+
getContacts: async ({apiKey, apiUrl}) => {
58+
// Fetch contacts using apiUrl and apiKey
59+
const response = await fetch(`${apiUrl}/api/contacts`, {
60+
headers: {Authorization: `Bearer ${apiKey}`},
61+
});
62+
63+
if (response.status === 401) {
64+
throw new ServerError(401, "Unauthorized");
65+
}
66+
67+
if (!response.ok) {
68+
throw new ServerError(500, "Could not fetch contacts");
69+
}
70+
71+
// TODO: Convert contacts to the structure below
72+
const contacts = await response.json();
73+
return contacts;
74+
},
5575
};
5676

5777
bridge.start(adapter);
@@ -61,21 +81,37 @@ Contacts are accepted in this format:
6181

6282
```js
6383
{
64-
id: "abc123",
65-
// Provide either the full name or first and last name, not both
66-
name: null, // or null
67-
firstName: "Walter", // or null
68-
lastName: "Geoffrey", // or null
69-
organization: "Rocket Science Inc.", // or null
70-
contactUrl: "http://myapp.com/contacts/abc123", // or null
71-
avatarUrl: "http://myapp.com/avatar/abc123.png", // or null
72-
email: "[email protected]", // or null
73-
phoneNumbers: [
74-
{
75-
label: "MOBILE", // or "WORK" or "HOME"
76-
phoneNumber: "+4915799912345"
77-
}
78-
]
84+
id: "abc123",
85+
// Provide either the full name or first and last name, not both
86+
name
87+
:
88+
null, // or null
89+
firstName
90+
:
91+
"Walter", // or null
92+
lastName
93+
:
94+
"Geoffrey", // or null
95+
organization
96+
:
97+
"Rocket Science Inc.", // or null
98+
contactUrl
99+
:
100+
"http://myapp.com/contacts/abc123", // or null
101+
avatarUrl
102+
:
103+
"http://myapp.com/avatar/abc123.png", // or null
104+
email
105+
:
106+
"[email protected]", // or null
107+
phoneNumbers
108+
:
109+
[
110+
{
111+
label: "MOBILE", // or "WORK" or "HOME"
112+
phoneNumber: "+4915799912345"
113+
}
114+
]
79115
}
80116
```
81117

@@ -87,4 +123,5 @@ The sipgate Integration Bridge supports configuration through the following envi
87123
- `OAUTH2_IDENTIFIER`: Name of the Integration to identify credentials in uppercase e. g. "MY_CRM"
88124
- `REDIS_URL`: URL of a Redis instance to cache responses, otherwise memory cache will be used
89125
- `CACHE_DISABLED`: Disable caching
90-
- `CACHE_REFRESH_INTERVAL`: Time a contact in cache is not refreshed (in seconds), only used if redis or memory cache is active
126+
- `CACHE_REFRESH_INTERVAL`: Time a contact in cache is not refreshed (in seconds), only used if redis or memory cache is
127+
active

package.json

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,6 @@
88
"dist"
99
],
1010
"repository": "github:sipgate/integration-bridge",
11-
"contributors": [
12-
{
13-
"name": "Benjamin Kluck",
14-
"email": "[email protected]"
15-
},
16-
{
17-
"name": "Felix Gladisch",
18-
"email": "[email protected]"
19-
}
20-
],
2111
"bugs": {
2212
"url": "https://github.com/sipgate/integration-bridge/issues"
2313
},
@@ -28,17 +18,17 @@
2818
],
2919
"license": "UNLICENSED",
3020
"scripts": {
21+
"dev": "npm run build && npm link && tsc --watch",
3122
"prepare": "husky install",
3223
"test": "jest",
3324
"compile": "tsc",
3425
"build": "npm test && rimraf dist && npm run compile",
3526
"precommit": "lint-staged",
3627
"prepublishOnly": "npm run build",
37-
"format": "prettier --write '**/*.ts'",
38-
"link": "npm link"
28+
"format": "prettier --write '**/*.ts'"
3929
},
4030
"lint-staged": {
41-
"*.{ts}": [
31+
"*.ts": [
4232
"prettier --write",
4333
"git add"
4434
]

src/index.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import cors from "cors";
44
import express from "express";
55
import { Server } from "http";
66
import { errorHandlerMiddleware, extractHeaderMiddleware } from "./middlewares";
7-
import { Adapter, ContactCache, Controller } from "./models";
7+
import {
8+
Adapter,
9+
ContactCache,
10+
Controller,
11+
IntegrationEntityBridgeRequest,
12+
} from "./models";
813
import { CustomRouter } from "./models/custom-router.model";
914
import { getContactCache } from "./util/get-contact-cache";
1015

@@ -32,7 +37,6 @@ export function start(
3237
cache = getContactCache();
3338

3439
const controller: Controller = new Controller(adapter, cache);
35-
3640
app.get("/contacts", (req, res, next) =>
3741
controller.getContacts(req, res, next)
3842
);
@@ -45,6 +49,11 @@ export function start(
4549
app.delete("/contacts/:id", (req, res, next) =>
4650
controller.deleteContact(req, res, next)
4751
);
52+
app.get(
53+
"/entity/:type/:id",
54+
(req: IntegrationEntityBridgeRequest, res, next) =>
55+
controller.getEntity(req, res, next)
56+
);
4857
app.get("/calendar", (req, res, next) =>
4958
controller.getCalendarEvents(req, res, next)
5059
);

src/models/adapter.model.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import {
88
Contact,
99
ContactTemplate,
1010
ContactUpdate,
11+
LabeledIntegrationEntity,
1112
} from ".";
13+
import { IntegrationEntityType } from "./integration-entity.model";
1214

1315
export interface Adapter {
1416
getToken?: (config: Config) => Promise<{ apiKey: string }>;
@@ -43,6 +45,11 @@ export interface Adapter {
4345
) => Promise<void>;
4446
deleteCalendarEvent?: (config: Config, id: string) => Promise<void>;
4547
handleCallEvent?: (config: Config, event: CallEvent) => Promise<string>;
48+
getEntity?: (
49+
providerConfig: Config,
50+
id: string,
51+
type: IntegrationEntityType
52+
) => Promise<LabeledIntegrationEntity>;
4653
handleConnectedEvent?: (config: Config) => Promise<void>;
4754
getHealth?: () => Promise<void>;
4855
getOAuth2RedirectUrl?: (req?: Request, res?: Response) => Promise<string>;

src/models/bridge-request.model.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
import { IntegrationEntityType } from "./integration-entity.model";
12
import { Request } from "express";
23
import { Config } from "./config.model";
3-
import { UpdateCallEventBody } from "./call-event.model";
44

55
export interface BridgeRequest extends Request {
66
providerConfig?: Config;
77
}
88

9-
export interface UpdateCallEventBridgeRequest extends BridgeRequest {
10-
body: UpdateCallEventBody;
9+
export interface IntegrationEntityBridgeRequest extends BridgeRequest {
10+
params: { id: string; type: IntegrationEntityType };
1111
}

src/models/contact.model.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { IntegrationEntity } from "./integration-entity.model";
2+
13
export enum PhoneNumberLabel {
24
WORK = "WORK",
35
MOBILE = "MOBILE",
@@ -37,6 +39,7 @@ export type ContactResult = {
3739
contactUrl: string | null;
3840
avatarUrl: string | null;
3941
readonly?: boolean;
42+
relatesTo?: IntegrationEntity[];
4043
};
4144

4245
export type ContactTemplate = BaseContact & {

src/models/controller.model.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { NextFunction, Request, Response } from "express";
33
import { stringify } from "querystring";
44
import {
55
Adapter,
6-
BridgeRequest,
76
CalendarEvent,
87
CalendarEventTemplate,
98
CallEvent,
@@ -16,12 +15,16 @@ import {
1615
import { calendarEventsSchema, contactsSchema } from "../schemas";
1716
import { anonymizeKey } from "../util/anonymize-key";
1817
import { shouldSkipCallEvent } from "../util/call-event.util";
18+
import { errorLogger, infoLogger } from "../util/logger.util";
1919
import { parsePhoneNumber } from "../util/phone-number-utils";
2020
import { validate } from "../util/validate";
2121
import { APIContact } from "./api-contact.model";
22+
import {
23+
BridgeRequest,
24+
IntegrationEntityBridgeRequest,
25+
} from "./bridge-request.model";
2226
import { CacheItemStateType } from "./cache-item-state.model";
2327
import { CalendarFilterOptions } from "./calendar-filter-options.model";
24-
import { errorLogger, infoLogger } from "../util/logger.util";
2528
import { IntegrationErrorType } from "./integration-error.model";
2629

2730
const CONTACT_FETCH_TIMEOUT: number = 3000;
@@ -585,6 +588,36 @@ export class Controller {
585588
}
586589
}
587590

591+
public async getEntity(
592+
req: IntegrationEntityBridgeRequest,
593+
res: Response,
594+
next: NextFunction
595+
): Promise<void> {
596+
const { providerConfig } = req;
597+
try {
598+
if (!providerConfig) {
599+
throw new ServerError(400, "Missing parameters");
600+
}
601+
602+
if (!this.adapter.getEntity) {
603+
throw new ServerError(501, "Fetching Entity is not implemented");
604+
}
605+
606+
const fetchedEntity = await this.adapter.getEntity(
607+
providerConfig,
608+
req.params.id,
609+
req.params.type
610+
);
611+
612+
infoLogger(`[${fetchedEntity}] `, providerConfig);
613+
614+
res.status(200).send(fetchedEntity);
615+
} catch (error) {
616+
errorLogger("Could not get entity:", providerConfig, error);
617+
next(error);
618+
}
619+
}
620+
588621
public async getHealth(
589622
req: BridgeRequest,
590623
res: Response,

src/models/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export * from "./custom-router.model";
1212
export * from "./server-error.model";
1313
export * from "./user.model";
1414
export * from "./integration-error.model";
15+
export * from "./integration-entity.model";
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export type IntegrationEntity = {
2+
id: string;
3+
type: IntegrationEntityType;
4+
source: string;
5+
};
6+
7+
export type LabeledIntegrationEntity = IntegrationEntity & {
8+
label: string;
9+
};
10+
11+
export enum IntegrationEntityType {
12+
DEALS = "deals",
13+
COMPANIES = "companies",
14+
}

0 commit comments

Comments
 (0)