Skip to content

Commit

Permalink
Feat/service dashboard (#2747)
Browse files Browse the repository at this point in the history
* start service for dashboard

* wip add route operators-by-month

* wip add operator by day route

* add incentive routes

* add campaigns route

* wip chart

* start service for dashboard

* wip add route operators-by-month

* wip add operator by day route

* add incentive routes

* add campaigns route

* wip chart

* wip operator graph by month

* feat: add territories route and selectTerritory in front

* feat: add journeys graph

* fix: remove empty territories

* wip table campaigns

* wip table pagination

* feat: apdf table and endpoint

* wip export

* wip users

* add: operator_name and territory_name

* add territories and operators in front admin

* feat: start implement superstruct validator

* CampaignsAction to superstruct

* wip superstruct in actions

* feat: finish superstruct implementation

* fix: implement sql  function in providers

* feat: add temp permissions for merging

* fix: sql values

* fix: correction from coderabbit suggestions
  • Loading branch information
Datayama38 authored Jan 22, 2025
1 parent 95624be commit 77ad631
Show file tree
Hide file tree
Showing 65 changed files with 3,056 additions and 106 deletions.
1 change: 1 addition & 0 deletions api/src/lib/superstruct/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "jsr:@superstruct/core@^2";
7 changes: 6 additions & 1 deletion api/src/pdc/providers/superstruct/ValidatorMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ResultType,
UnexpectedException,
} from "@/ilos/common/index.ts";
import { validate } from "@/lib/superstruct/index.ts";

@middleware()
export class ValidatorMiddleware implements MiddlewareInterface {
Expand All @@ -18,7 +19,11 @@ export class ValidatorMiddleware implements MiddlewareInterface {
schema: superstruct.Struct<unknown>,
): Promise<ResultType> {
try {
superstruct.assert(params, schema);
const [err, data] = validate(params, schema, { coerce: true });
if (err) {
throw err;
}
return next(data, context);
} catch (e) {
if (e instanceof superstruct.StructError) {
throw new InvalidParamsException(e.failures().map((f) => `${f.path} : ${f.message}`));
Expand Down
27 changes: 27 additions & 0 deletions api/src/pdc/providers/superstruct/shared/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { coerce, date, enums, integer, pattern, size, string } from "@/lib/superstruct/index.ts";

export const Serial = size(integer(), 0, 2147483647);
export const DateOnly = coerce(
date(),
pattern(string(), /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/),
(v) => new Date(v).toISOString().split("T")[0],
);

export const Timestamp = coerce(
date(),
pattern(string(), /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$/),
(v) => new Date(v),
);
export const Varchar = size(string(), 0, 256);

export const Uuid = pattern(
string(),
/^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/,
);

export const Phone = pattern(Varchar, /^\+[0-9]{6,20}$/);

export const IdentityKey = pattern(string(), /^[a-f0-9]{64}$/);
export const Direction = enums(["from", "to", "both"]);
export const Year = size(integer(), 2020, new Date().getFullYear());
export const Id = coerce(Serial, string(), (v) => Math.abs(parseInt(v, 10)));
52 changes: 52 additions & 0 deletions api/src/pdc/proxy/HttpTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export class HttpTransport implements TransportInterface {
this.registerSimulationRoutes();
this.registerCeeRoutes();
this.registerHonorRoutes();
this.registerDashboardRoutes();
this.registerObservatoryRoutes();
this.registerContactformRoute();
this.registerCallHandler();
Expand Down Expand Up @@ -866,6 +867,57 @@ export class HttpTransport implements TransportInterface {
);
}

private registerDashboardRoutes() {
const routes: Array<RouteParams> = [
{
path: "/dashboard/users",
action: "dashboard:users",
method: "GET",
},
{
path: "/dashboard/operators",
action: "dashboard:operators",
method: "GET",
},
{
path: "/dashboard/operators/month",
action: "dashboard:operatorsByMonth",
method: "GET",
},
{
path: "/dashboard/operators/day",
action: "dashboard:operatorsByDay",
method: "GET",
},
{
path: "/dashboard/incentive/month",
action: "dashboard:incentiveByMonth",
method: "GET",
},
{
path: "/dashboard/incentive/day",
action: "dashboard:incentiveByDay",
method: "GET",
},
{
path: "/dashboard/campaigns",
action: "dashboard:campaigns",
method: "GET",
},
{
path: "/dashboard/campaign-apdf",
action: "dashboard:campaignApdf",
method: "GET",
},
{
path: "/dashboard/territories",
action: "dashboard:territories",
method: "GET",
},
];
routes.map((c) => registerExpressRoute(this.app, this.kernel, c));
}

private registerObservatoryRoutes() {
type ObservatoryMethod = string;
type ObservatoryURL = string;
Expand Down
2 changes: 2 additions & 0 deletions api/src/pdc/proxy/Kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ApplicationServiceProvider } from "../services/application/ApplicationS
import { CeeServiceProvider } from "../services/cee/CeeServiceProvider.ts";
import { CertificateServiceProvider } from "../services/certificate/CertificateServiceProvider.ts";
import { CompanyServiceProvider } from "../services/company/CompanyServiceProvider.ts";
import { DashboardServiceProvider } from "../services/dashboard/DashboardServiceProvider.ts";
import { ExportServiceProvider } from "../services/export/ExportServiceProvider.ts";
import { GeoServiceProvider } from "../services/geo/GeoServiceProvider.ts";
import { HonorServiceProvider } from "../services/honor/HonorServiceProvider.ts";
Expand Down Expand Up @@ -39,6 +40,7 @@ import { config } from "./config/index.ts";
TerritoryServiceProvider,
UserServiceProvider,
ObservatoryServiceProvider,
DashboardServiceProvider,
GeoServiceProvider,
],
commands: [ListCommand],
Expand Down
49 changes: 49 additions & 0 deletions api/src/pdc/services/dashboard/DashboardServiceProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* eslint-disable max-len */
import { serviceProvider } from "@/ilos/common/index.ts";
import { ServiceProvider as AbstractServiceProvider } from "@/ilos/core/index.ts";
import { defaultMiddlewareBindings } from "@/pdc/providers/middleware/index.ts";
import { S3StorageProvider } from "@/pdc/providers/storage/index.ts";
import { ValidatorMiddleware } from "@/pdc/providers/superstruct/ValidatorMiddleware.ts";
import { CampaignApdfAction } from "@/pdc/services/dashboard/actions/CampaignApdfAction.ts";
import { CampaignsAction } from "@/pdc/services/dashboard/actions/CampaignsAction.ts";
import { IncentiveByDayAction } from "@/pdc/services/dashboard/actions/IncentiveByDayAction.ts";
import { OperatorsAction } from "@/pdc/services/dashboard/actions/OperatorsAction.ts";
import { OperatorsByDayAction } from "@/pdc/services/dashboard/actions/OperatorsByDayAction.ts";
import { TerritoriesAction } from "@/pdc/services/dashboard/actions/TerritoriesAction.ts";
import { UsersAction } from "@/pdc/services/dashboard/actions/UsersAction.ts";
import { CampaignsRepositoryProvider } from "@/pdc/services/dashboard/providers/CampaignsRepositoryProvider.ts";
import { IncentiveRepositoryProvider } from "@/pdc/services/dashboard/providers/IncentiveRepositoryProvider.ts";
import { TerritoriesRepositoryProvider } from "@/pdc/services/dashboard/providers/TerritoriesRepositoryProvider.ts";
import { IncentiveByMonthAction } from "./actions/IncentiveByMonthAction.ts";
import { OperatorsByMonthAction } from "./actions/OperatorsByMonthAction.ts";
import { OperatorsRepositoryProvider } from "./providers/OperatorsRepositoryProvider.ts";
import { UsersRepositoryProvider } from "./providers/UsersRepositoryProvider.ts";

/* eslint-enable */
@serviceProvider({
commands: [],
providers: [
S3StorageProvider,
OperatorsRepositoryProvider,
IncentiveRepositoryProvider,
CampaignsRepositoryProvider,
UsersRepositoryProvider,
TerritoriesRepositoryProvider,
],
handlers: [
OperatorsByMonthAction,
OperatorsByDayAction,
IncentiveByMonthAction,
IncentiveByDayAction,
CampaignsAction,
CampaignApdfAction,
TerritoriesAction,
OperatorsAction,
UsersAction,
],
middlewares: [...defaultMiddlewareBindings, [
"validate",
ValidatorMiddleware,
]],
})
export class DashboardServiceProvider extends AbstractServiceProvider {}
37 changes: 37 additions & 0 deletions api/src/pdc/services/dashboard/actions/CampaignApdfAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { handler } from "@/ilos/common/index.ts";
import { Action as AbstractAction } from "@/ilos/core/index.ts";
import { copyGroupIdAndApplyGroupPermissionMiddlewares } from "@/pdc/providers/middleware/index.ts";
import { CampaignApdf } from "@/pdc/services/dashboard/dto/CampaignApdf.ts";
import { CampaignsRepositoryInterfaceResolver } from "@/pdc/services/dashboard/interfaces/CampaignsRepositoryProviderInterface.ts";

export type ResultInterface = {
signed_url: string;
key: string;
size: number;
operator_id: number;
campaign_id: number;
datetime: Date;
name: string;
}[];

@handler({
service: "dashboard",
method: "campaignApdf",
middlewares: [
["validate", CampaignApdf],
...copyGroupIdAndApplyGroupPermissionMiddlewares({
territory: "territory.apdf.list",
operator: "operator.apdf.list",
registry: "registry.apdf.list",
}),
],
})
export class CampaignApdfAction extends AbstractAction {
constructor(private repository: CampaignsRepositoryInterfaceResolver) {
super();
}

public async handle(params: CampaignApdf): Promise<ResultInterface> {
return this.repository.getCampaignApdf(params);
}
}
38 changes: 38 additions & 0 deletions api/src/pdc/services/dashboard/actions/CampaignsAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { handler } from "@/ilos/common/index.ts";
import { Action as AbstractAction } from "@/ilos/core/index.ts";
import { hasPermissionMiddleware } from "@/pdc/providers/middleware/middlewares.ts";
import { Campaigns } from "@/pdc/services/dashboard/dto/Campaigns.ts";
import { CampaignsRepositoryInterfaceResolver } from "@/pdc/services/dashboard/interfaces/CampaignsRepositoryProviderInterface.ts";

export type ResultInterface = {
id: string;
start_date: Date;
end_date: Date;
territory_id: string;
territory_name: string;
name: string;
description: string;
unit: string;
status: string;
handler: string;
incentive_sum: number;
max_amount: number;
}[];

@handler({
service: "dashboard",
method: "campaigns",
middlewares: [
["validate", Campaigns],
hasPermissionMiddleware("common.policy.list"),
],
})
export class CampaignsAction extends AbstractAction {
constructor(private repository: CampaignsRepositoryInterfaceResolver) {
super();
}

public async handle(params: Campaigns): Promise<ResultInterface> {
return this.repository.getCampaigns(params);
}
}
33 changes: 33 additions & 0 deletions api/src/pdc/services/dashboard/actions/IncentiveByDayAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { handler } from "@/ilos/common/index.ts";
import { Action as AbstractAction } from "@/ilos/core/index.ts";
import { Infer } from "@/lib/superstruct/index.ts";
import { hasPermissionMiddleware } from "@/pdc/providers/middleware/middlewares.ts";
import { Direction } from "@/pdc/providers/superstruct/shared/index.ts";
import { IncentiveByDay } from "@/pdc/services/dashboard/dto/IncentiveByDay.ts";
import { IncentiveRepositoryInterfaceResolver } from "@/pdc/services/dashboard/interfaces/IncentiveRepositoryProviderInterface.ts";
export type ResultInterface = {
date: Date;
territory_id: number;
direction: Infer<typeof Direction>;
journeys: number;
incented_journeys: number;
incentive_amount: number;
}[];

@handler({
service: "dashboard",
method: "incentiveByDay",
middlewares: [
["validate", IncentiveByDay],
hasPermissionMiddleware("common.observatory.stats"),
],
})
export class IncentiveByDayAction extends AbstractAction {
constructor(private repository: IncentiveRepositoryInterfaceResolver) {
super();
}

public async handle(params: IncentiveByDay): Promise<ResultInterface> {
return this.repository.getIncentiveByDay(params);
}
}
34 changes: 34 additions & 0 deletions api/src/pdc/services/dashboard/actions/IncentiveByMonthAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { handler } from "@/ilos/common/index.ts";
import { Action as AbstractAction } from "@/ilos/core/index.ts";
import { Infer } from "@/lib/superstruct/index.ts";
import { hasPermissionMiddleware } from "@/pdc/providers/middleware/middlewares.ts";
import { Direction } from "@/pdc/providers/superstruct/shared/index.ts";
import { IncentiveByMonth } from "@/pdc/services/dashboard/dto/IncentiveByMonth.ts";
import { IncentiveRepositoryInterfaceResolver } from "@/pdc/services/dashboard/interfaces/IncentiveRepositoryProviderInterface.ts";
export type ResultInterface = {
year: number;
month: number;
territory_id: string;
direction: Infer<typeof Direction>;
journeys: number;
incented_journeys: number;
incentive_amount: number;
}[];

@handler({
service: "dashboard",
method: "incentiveByMonth",
middlewares: [
["validate", IncentiveByMonth],
hasPermissionMiddleware("common.observatory.stats"),
],
})
export class IncentiveByMonthAction extends AbstractAction {
constructor(private repository: IncentiveRepositoryInterfaceResolver) {
super();
}

public async handle(params: IncentiveByMonth): Promise<ResultInterface> {
return this.repository.getIncentiveByMonth(params);
}
}
29 changes: 29 additions & 0 deletions api/src/pdc/services/dashboard/actions/OperatorsAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { handler } from "@/ilos/common/index.ts";
import { Action as AbstractAction } from "@/ilos/core/index.ts";
import { hasPermissionMiddleware } from "@/pdc/providers/middleware/middlewares.ts";
import { Operators } from "@/pdc/services/dashboard/dto/Operators.ts";
import { OperatorsRepositoryInterfaceResolver } from "@/pdc/services/dashboard/interfaces/OperatorsRepositoryProviderInterface.ts";
export type ResultInterface = {
id: number;
name: string;
legal_name: string;
siret: number;
}[];

@handler({
service: "dashboard",
method: "operators",
middlewares: [
["validate", Operators],
hasPermissionMiddleware("common.operator.list"),
],
})
export class OperatorsAction extends AbstractAction {
constructor(private repository: OperatorsRepositoryInterfaceResolver) {
super();
}

public async handle(params: Operators): Promise<ResultInterface> {
return this.repository.getOperators(params);
}
}
35 changes: 35 additions & 0 deletions api/src/pdc/services/dashboard/actions/OperatorsByDayAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { handler } from "@/ilos/common/index.ts";
import { Action as AbstractAction } from "@/ilos/core/index.ts";
import { Infer } from "@/lib/superstruct/index.ts";
import { hasPermissionMiddleware } from "@/pdc/providers/middleware/middlewares.ts";
import { Direction } from "@/pdc/providers/superstruct/shared/index.ts";
import { OperatorsByDay } from "@/pdc/services/dashboard/dto/OperatorsByDay.ts";
import { OperatorsRepositoryInterfaceResolver } from "@/pdc/services/dashboard/interfaces/OperatorsRepositoryProviderInterface.ts";
export type ResultInterface = {
date: Date;
territory_id: string;
direction: Infer<typeof Direction>;
operator_id: number;
operator_name: string;
journeys: number;
incented_journeys: number;
incentive_amount: number;
}[];

@handler({
service: "dashboard",
method: "operatorsByDay",
middlewares: [
["validate", OperatorsByDay],
hasPermissionMiddleware("common.observatory.stats"),
],
})
export class OperatorsByDayAction extends AbstractAction {
constructor(private repository: OperatorsRepositoryInterfaceResolver) {
super();
}

public async handle(params: OperatorsByDay): Promise<ResultInterface> {
return this.repository.getOperatorsByDay(params);
}
}
Loading

0 comments on commit 77ad631

Please sign in to comment.