Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/reference/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5398,6 +5398,33 @@ paths:
required: true
schema:
type: string
'/dossiers/{campaign}/costs':
parameters:
- schema:
type: string
name: campaign
in: path
required: true
get:
summary: Your GET endpoint
tags: []
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
totalCost:
type: number
operationId: get-dossiers-campaign-costs
x-stoplight:
id: q9l4m7dj053wk
security:
- JWT: []
parameters:
- $ref: '#/components/parameters/filterBy'
/education:
get:
operationId: get-education
Expand Down
178 changes: 178 additions & 0 deletions src/routes/dossiers/campaignId/costs/_get/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import app from "@src/app";
import { tryber } from "@src/features/database";
import request from "supertest";

const campaign_1 = {
id: 1,
project_id: 1,
platform_id: 1,
start_date: "2023-01-13 10:10:10",
end_date: "2023-01-14 10:10:10",
title: "",
page_preview_id: 1,
page_manual_id: 1,
customer_id: 1,
pm_id: 1,
customer_title: "",
tokens_usage: 25,
};
const campaign_2 = {
...campaign_1,
id: 2,
project_id: 1,
tokens_usage: 10,
};
const campaign_3 = {
...campaign_1,
id: 3,
project_id: 1,
tokens_usage: 5,
};
const project = {
display_name: "",
edited_by: 1,
};

const payment_1 = {
id: 11,
tester_id: 1,
campaign_id: 1,
amount: 100.5,
is_paid: 1,
is_requested: 1,
work_type: "test",
note: "note",
receipt_id: -1,
work_type_id: 1,
};

const payment_2 = {
...payment_1,
id: 22,
tester_id: 2,
campaign_id: 1,
amount: 200.75,
is_paid: 1,
is_requested: 1,
work_type_id: 2,
};

const payment_3 = {
...payment_1,
id: 33,
tester_id: 3,
campaign_id: 2,
amount: 300.25,
is_paid: 1,
is_requested: 1,
};

describe("GET /dossiers/campaignId/costs", () => {
beforeAll(async () => {
await tryber.tables.WpAppqEvdCampaign.do().insert([
campaign_1,
campaign_2,
campaign_3,
]);
await tryber.tables.WpAppqProject.do().insert([
{
...project,
display_name: "Project 1",
id: 1,
customer_id: 1,
},
{
...project,
display_name: "Project 3",
id: 3,
customer_id: 2,
},
]);
await tryber.tables.WpAppqPayment.do().insert([
payment_1,
payment_2,
payment_3,
]);
});
afterAll(async () => {
await tryber.tables.WpAppqCustomer.do().delete();
});

it("Should answer 403 if not logged in", () => {
return request(app).get("/dossiers/1/costs").expect(403);
});
it("Should answer 403 if logged in without permissions", async () => {
const response = await request(app)
.get("/dossiers/1/costs")
.set("Authorization", "Bearer tester");
expect(response.status).toBe(403);
});
it("Should answer 400 if campaign does not exists", async () => {
const response = await request(app)
.get("/dossiers/100/costs")
.set("Authorization", "Bearer tester");
expect(response.status).toBe(400);
});

it("Should answer 403 if logged as user without permissions on the campaign", async () => {
const response = await request(app)
.get("/dossiers/2/costs")
.set("Authorization", 'Bearer tester olp {"appq_campaign":[1]}');
expect(response.status).toBe(403);
});

it("Should answer 200 if logged as user with full access on the campaign", async () => {
const response = await request(app)
.get("/dossiers/1/costs")
.set("Authorization", 'Bearer tester olp {"appq_campaign":[1]}');
expect(response.status).toBe(200);
});

it("Should answer with the campaigns costs", async () => {
const response = await request(app)
.get("/dossiers/1/costs")
.set("Authorization", 'Bearer tester olp {"appq_campaign":[1]}');
expect(response.status).toBe(200);
expect(response.body).toEqual({
totalCost: payment_1.amount + payment_2.amount,
});
});
it("Should filterBy the costs by work_type_id", async () => {
const response = await request(app)
.get("/dossiers/1/costs?filterBy[type]=2")
.set("Authorization", 'Bearer tester olp {"appq_campaign":[1]}');
expect(response.status).toBe(200);
expect(response.body).toEqual({
totalCost: payment_2.amount,
});
});

it("Should filterBy the costs by multiple work_type_id", async () => {
const response = await request(app)
.get("/dossiers/1/costs?filterBy[type]=1,2")
.set("Authorization", 'Bearer tester olp {"appq_campaign":[1]}');
expect(response.status).toBe(200);
expect(response.body).toEqual({
totalCost: payment_1.amount + payment_2.amount,
});
});

it("Should ignore the filter if it the type is invalid", async () => {
const response = await request(app)
.get("/dossiers/1/costs?filterBy[type]=invalid")
.set("Authorization", 'Bearer tester olp {"appq_campaign":[1]}');
expect(response.status).toBe(200);
expect(response.body).toEqual({
totalCost: payment_1.amount + payment_2.amount,
});
});
it("Should ignore the filter if it the filter is invalid", async () => {
const response = await request(app)
.get("/dossiers/1/costs?filterBy[invalid]=1")
.set("Authorization", 'Bearer tester olp {"appq_campaign":[1]}');
expect(response.status).toBe(200);
expect(response.body).toEqual({
totalCost: payment_1.amount + payment_2.amount,
});
});
});
86 changes: 86 additions & 0 deletions src/routes/dossiers/campaignId/costs/_get/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/** OPENAPI-CLASS : get-dossiers-campaign-costs */

import OpenapiError from "@src/features/OpenapiError";
import { tryber } from "@src/features/database";
import CampaignRoute from "@src/features/routes/CampaignRoute";

export default class RouteItem extends CampaignRoute<{
response: StoplightOperations["get-dossiers-campaign-costs"]["responses"]["200"]["content"]["application/json"];
parameters: StoplightOperations["get-dossiers-campaign-costs"]["parameters"]["path"];
query: StoplightOperations["get-dossiers-campaign-costs"]["parameters"]["query"];
}> {
private campaignId: number;
private filterBy: {
type?: string | string[];
} = {};

constructor(configuration: RouteClassConfiguration) {
super(configuration);
const query = this.getQuery();
this.campaignId = Number(this.getParameters().campaign);
if (query.filterBy) {
this.filterBy = query.filterBy;
}
}

protected async filter() {
if (!(await super.filter())) return false;

if (!this.hasAccessToCampaign(this.campaignId)) {
this.setError(403, new OpenapiError("You are not authorized to do this"));
return false;
}

if (!(await this.campaignExists())) {
this.setError(403, new OpenapiError("Campaign does not exist"));
return false;
}

return true;
}

private async campaignExists(): Promise<boolean> {
const campaign = await tryber.tables.WpAppqEvdCampaign.do()
.select("id")
.where({
id: this.campaignId,
})
.first();
if (!campaign) return false;

return true;
}

protected async prepare(): Promise<void> {
const cost = await this.calculateTotalCost();
return this.setSuccess(200, cost);
}

private async calculateTotalCost() {
let query = tryber.tables.WpAppqPayment.do().where({
campaign_id: this.campaignId,
});

if (this.filterBy && this.filterBy.type !== undefined) {
const rawTypes = Array.isArray(this.filterBy.type)
? this.filterBy.type
: [this.filterBy.type];

const types = rawTypes
.map((t) => Number(t))
.filter((t) => !Number.isNaN(t));

if (types.length > 0) {
query = query.whereIn("work_type_id", types);
}
}

const paymentsTotal = (await query
.sum("amount as totalAmount")
.first()) as unknown as { totalAmount: string | number | null };

return {
totalCost: paymentsTotal ? Number(paymentsTotal.totalAmount || 0) : 0,
};
}
}
29 changes: 29 additions & 0 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,14 @@ export interface paths {
};
};
};
"/dossiers/{campaign}/costs": {
get: operations["get-dossiers-campaign-costs"];
parameters: {
path: {
campaign: string;
};
};
};
"/education": {
/** Get all education levels */
get: operations["get-education"];
Expand Down Expand Up @@ -3229,6 +3237,27 @@ export interface operations {
};
};
};
"get-dossiers-campaign-costs": {
parameters: {
path: {
campaign: string;
};
query: {
/** Key-value Array for item filtering */
filterBy?: components["parameters"]["filterBy"];
};
};
responses: {
/** OK */
200: {
content: {
"application/json": {
totalCost?: number;
};
};
};
};
};
/** Get all education levels */
"get-education": {
responses: {
Expand Down
Loading