diff --git a/deployment/after-install.sh b/deployment/after-install.sh index 12b06657a..ec45cd2b1 100644 --- a/deployment/after-install.sh +++ b/deployment/after-install.sh @@ -45,6 +45,9 @@ services: environment: PORT: 80 API_ROOT: ${API_ROOT} + UNGUESS_API_ROOT: '${UNGUESS_API_ROOT}' + UNGUESS_API_USERNAME: '${UNGUESS_API_USERNAME}' + UNGUESS_API_PASSWORD: '${UNGUESS_API_PASSWORD}' SENDGRID_KEY: '${SENDGRID_KEY}' DEFAULT_SENDER_MAIL: '${DEFAULT_SENDER_MAIL}' DEFAULT_SENDER_NAME: '${DEFAULT_SENDER_NAME}' diff --git a/src/config.ts b/src/config.ts index c34fc60fb..a244e0c17 100644 --- a/src/config.ts +++ b/src/config.ts @@ -32,6 +32,11 @@ const config: { private: string; }; GOOGLE_API_KEY: string; + unguessApi?: { + basePath?: string; + username?: string; + password?: string; + }; } = { port: process.env.PORT || "3000", apiRoot: false, @@ -59,6 +64,11 @@ const config: { }, CROWD_URL: process.env.CROWD_URL || "https://tryber.me/", GOOGLE_API_KEY: process.env.GOOGLE_API_KEY || "", + unguessApi: { + basePath: process.env.UNGUESS_API_ROOT || "", + username: process.env.UNGUESS_API_USERNAME || "", + password: process.env.UNGUESS_API_PASSWORD || "", + }, }; if (process.env.SSL_CHAIN && process.env.SSL_PRIVATE) { diff --git a/src/features/class/Unguess.ts b/src/features/class/Unguess.ts new file mode 100644 index 000000000..e262ed454 --- /dev/null +++ b/src/features/class/Unguess.ts @@ -0,0 +1,88 @@ +import config from "@src/config"; + +class Unguess { + private baseUrl: string; + private username: string; + private password: string; + + constructor() { + const { basePath, username, password } = config.unguessApi || {}; + this.baseUrl = basePath || ""; + this.username = username || ""; + this.password = password || ""; + } + + /** + * Private method to fetch a token for API requests + */ + private async getToken(): Promise { + const response = await fetch(`${this.baseUrl}/authenticate`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + username: this.username, + password: this.password, + }), + }); + + if (!response.ok) { + throw new Error("Failed to authenticate: " + response.statusText); + } + + const data = await response.json(); + if (!data.token) { + throw new Error("Authentication failed: Token not found"); + } + + return data.token; + } + + /** + * Private method to perform authenticated POST requests + */ + private async authPost( + path: string, + body: Record + ): Promise { + const token = await this.getToken(); + const response = await fetch(`${this.baseUrl}${path}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error(`Failed to post to ${path}: ${response.statusText}`); + } + + return response.json(); + } + + /** + * Public method to post a new customer + */ + public async postCustomer({ + userId, + name, + }: { + userId: number; + name: string; + }): Promise<{ id: number; name: string }> { + const body = { + company: name, + pm_id: userId, + }; + const result = await this.authPost("/workspaces", body); + return { + id: result.id, + name: result.name, + }; + } +} + +export default Unguess; diff --git a/src/routes/customers/_post/index.spec.ts b/src/routes/customers/_post/index.spec.ts index 7ebe878c2..b4c0878ad 100644 --- a/src/routes/customers/_post/index.spec.ts +++ b/src/routes/customers/_post/index.spec.ts @@ -3,8 +3,40 @@ import { tryber } from "@src/features/database"; import request from "supertest"; describe("POST /customers", () => { + beforeEach(async () => { + jest + .spyOn(global, "fetch") + .mockImplementation( + async (url: RequestInfo | URL, options?: RequestInit) => { + if (typeof url === "string" && url.includes("authenticate")) { + return Promise.resolve({ + ok: true, + json: async () => ({ token: "token" }), + } as Response); + } else if (typeof url === "string" && url.includes("workspaces")) { + const newCustomer = await tryber.tables.WpAppqCustomer.do() + .insert({ + company: "New Customer", + pm_id: 1, + }) + .returning("id"); + + return Promise.resolve({ + ok: true, + json: async () => ({ + id: newCustomer[0].id, + name: "New Customer", + }), + } as Response); + } + return Promise.reject(new Error("Invalid URL")); + } + ); + }); + afterEach(async () => { await tryber.tables.WpAppqCustomer.do().delete(); + jest.restoreAllMocks(); }); it("Should answer 403 if not logged in", () => { @@ -13,6 +45,7 @@ describe("POST /customers", () => { .send({ name: "New project" }) .expect(403); }); + it("Should answer 403 if logged in without permissions", async () => { const response = await request(app) .post("/customers") @@ -20,6 +53,7 @@ describe("POST /customers", () => { .set("Authorization", "Bearer tester"); expect(response.status).toBe(403); }); + it("Should answer 201 if logged as user with full access on campaigns", async () => { const response = await request(app) .post("/customers") @@ -27,6 +61,7 @@ describe("POST /customers", () => { .set("Authorization", 'Bearer tester olp {"appq_campaign":true}'); expect(response.status).toBe(201); }); + it("Should answer 403 if logged as user with access to some campaigns", async () => { const response = await request(app) .post("/customers") @@ -54,7 +89,7 @@ describe("POST /customers", () => { const customers = getResponse.body; expect(customers).toHaveLength(1); - expect(customers[0].id).toBe(id); expect(customers[0].name).toBe(name); + expect(customers[0].id).toBe(id); }); }); diff --git a/src/routes/customers/_post/index.ts b/src/routes/customers/_post/index.ts index 7dccd76f4..58919fd9f 100644 --- a/src/routes/customers/_post/index.ts +++ b/src/routes/customers/_post/index.ts @@ -1,8 +1,8 @@ /** OPENAPI-CLASS : post-customers */ import OpenapiError from "@src/features/OpenapiError"; -import { tryber } from "@src/features/database"; import UserRoute from "@src/features/routes/UserRoute"; +import Unguess from "@src/features/class/Unguess"; class RouteItem extends UserRoute<{ response: StoplightOperations["post-customers"]["responses"]["200"]["content"]["application/json"]; @@ -18,6 +18,7 @@ class RouteItem extends UserRoute<{ this.setError(403, new OpenapiError("You are not authorized to do this")); return false; } + return true; } @@ -26,23 +27,23 @@ class RouteItem extends UserRoute<{ } protected async prepare(): Promise { - const customer = await this.createCustomer(); + const customer = await this.postCustomerUnguessApi(); + return this.setSuccess(201, customer); } - private async createCustomer() { - const customer = await tryber.tables.WpAppqCustomer.do() - .insert({ - company: this.getBody().name, - pm_id: 0, - }) - .returning("id"); - const id = customer[0].id ?? customer[0]; - - return { - id: id, - name: this.getBody().name, - }; + private async postCustomerUnguessApi() { + const unguess = new Unguess(); + + try { + const customer = await unguess.postCustomer({ + userId: this.getTesterId(), + name: this.getBody().name, + }); + return customer; + } catch (error) { + console.error("Error creating customer:", error); + } } }